import React, { useState, useEffect } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { Search, Download, RefreshCw, AlertCircle, CheckCircle, Clock, XCircle, Eye, RotateCcw } from 'lucide-react'; import { LoadingSpinner } from './LoadingSpinner'; import { ErrorMessage } from './ErrorMessage'; import { CustomSelect } from './CustomSelect'; // 类型定义 interface AiAnalysisLogItem { id: string; log_type: string; title: string; status: string; status_display: string; details: string; error_message?: string; confidence?: number; quality_score?: number; category?: string; video_file_path?: string; retry_count?: number; created_at: string; updated_at: string; } interface AiAnalysisLogResponse { logs: AiAnalysisLogItem[]; total_count: number; current_page: number; page_size: number; total_pages: number; } interface AiAnalysisLogStats { total_records: number; successful_classifications: number; failed_classifications: number; needs_review: number; total_tasks: number; completed_tasks: number; failed_tasks: number; processing_tasks: number; average_confidence: number; average_quality_score: number; recent_24h_activity: number; } interface AiAnalysisLogViewerProps { projectId: string; } /** * AI分析日志查看器组件 * 遵循 Tauri 开发规范的前端组件设计模式 */ export const AiAnalysisLogViewer: React.FC = ({ projectId }) => { const [logs, setLogs] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // 查询参数 const [logType, setLogType] = useState<'records' | 'tasks'>('records'); const [statusFilter, setStatusFilter] = useState(''); const [searchKeyword, setSearchKeyword] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [pageSize] = useState(20); const [totalPages, setTotalPages] = useState(0); const [totalCount, setTotalCount] = useState(0); // 过滤选项 const [filterOptions, setFilterOptions] = useState(null); // 加载过滤选项 useEffect(() => { const loadFilterOptions = async () => { try { const options = await invoke('get_ai_analysis_log_filters'); setFilterOptions(options); } catch (err) { console.error('加载过滤选项失败:', err); } }; loadFilterOptions(); }, []); // 加载日志数据 const loadLogs = async () => { if (!projectId) return; setLoading(true); setError(null); try { const query = { project_id: projectId, log_type: logType, status_filter: statusFilter || null, search_keyword: searchKeyword || null, page: currentPage, page_size: pageSize, }; const response: AiAnalysisLogResponse = await invoke('get_ai_analysis_logs', { query }); setLogs(response.logs); setTotalCount(response.total_count); setTotalPages(response.total_pages); } catch (err) { setError(err as string); } finally { setLoading(false); } }; // 加载统计数据 const loadStats = async () => { if (!projectId) return; try { const statsData: AiAnalysisLogStats = await invoke('get_ai_analysis_stats', { projectId }); setStats(statsData); } catch (err) { console.error('加载统计数据失败:', err); } }; // 初始加载 useEffect(() => { loadLogs(); loadStats(); }, [projectId, logType, statusFilter, currentPage]); // 搜索处理 const handleSearch = () => { setCurrentPage(1); loadLogs(); }; // 重置搜索 const handleResetSearch = () => { setSearchKeyword(''); setStatusFilter(''); setCurrentPage(1); loadLogs(); }; // 重试失败任务 const handleRetryTask = async (taskId: string) => { try { await invoke('retry_failed_classification_task', { taskId }); loadLogs(); // 重新加载数据 } catch (err) { console.error('重试任务失败:', err); } }; // 获取状态图标 const getStatusIcon = (status: string, logType: string) => { if (logType === 'record') { switch (status) { case '"Classified"': return ; case '"Failed"': return ; case '"NeedsReview"': return ; default: return ; } } else { switch (status) { case '"Completed"': return ; case '"Failed"': return ; case '"Pending"': case '"Uploading"': case '"Analyzing"': return ; default: return ; } } }; // 获取状态颜色类 const getStatusColorClass = (status: string) => { if (status.includes('Classified') || status.includes('Completed')) { return 'bg-green-100 text-green-800'; } else if (status.includes('Failed')) { return 'bg-red-100 text-red-800'; } else if (status.includes('NeedsReview')) { return 'bg-yellow-100 text-yellow-800'; } else { return 'bg-blue-100 text-blue-800'; } }; if (error) { return ; } return (
{/* 统计卡片 */} {stats && (
总记录数
{stats.total_records}
成功分类
{stats.successful_classifications}
失败任务
{stats.failed_tasks}
平均置信度
{(stats.average_confidence * 100).toFixed(1)}%
)} {/* 控制栏 */}
{/* 日志类型切换 */}
{/* 搜索和过滤 */}
setSearchKeyword(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* 状态过滤 */} {filterOptions && ( setStatusFilter(e)} /> )}
{/* 日志列表 */}
{loading ? (

加载日志中...

) : logs.length === 0 ? (
暂无日志数据
) : (
{logs.map((log) => (
{getStatusIcon(log.status, log.log_type)}

{log.title}

{log.status_display}

{log.details}

{log.error_message && (

{log.error_message}

)}
{new Date(log.created_at).toLocaleString('zh-CN')} {log.confidence && ( 置信度: {(log.confidence * 100).toFixed(1)}% )} {log.quality_score && ( 质量: {(log.quality_score * 10).toFixed(1)}/10 )} {log.retry_count !== undefined && log.retry_count > 0 && ( 重试: {log.retry_count}次 )}
{/* 操作按钮 */}
{log.log_type === 'task' && log.status.includes('Failed') && ( )}
))}
)} {/* 分页 */} {totalPages > 1 && (
显示 {((currentPage - 1) * pageSize) + 1} 到 {Math.min(currentPage * pageSize, totalCount)} 条, 共 {totalCount} 条记录
{currentPage} / {totalPages}
)}
); };