Files
mixvideo-v2/apps/desktop/src/components/AiClassificationRealTimePreview.tsx
imeepos 3d24f5b908 fix: 修复按顺序匹配功能的TypeScript编译错误
修复内容:
- 在AiClassificationFormErrors接口中添加weight字段
- 修复AiClassificationRealTimePreview组件中缺少weight字段的问题
- 更新所有测试文件中的mock数据,添加weight字段
- 确保所有AI分类相关的类型定义完整一致

构建状态:
-  TypeScript编译通过
-  Rust编译通过
-  生成可执行文件和安装包
-  仅有编译警告,无错误

所有按顺序匹配功能现已可正常使用
2025-07-23 20:42:08 +08:00

222 lines
7.6 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, { useState, useMemo } from 'react';
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
import {
AiClassification,
AiClassificationFormData
} from '../types/aiClassification';
interface AiClassificationRealTimePreviewProps {
/** 当前表单数据 */
currentFormData: AiClassificationFormData;
/** 现有分类列表 */
existingClassifications: AiClassification[];
/** 是否为编辑模式 */
isEdit?: boolean;
/** 编辑中的分类ID */
editingClassificationId?: string;
}
/**
* AI分类实时预览组件
* 遵循前端开发规范的预览组件设计,实现分类修改时的提示词实时预览
*/
export const AiClassificationRealTimePreview: React.FC<AiClassificationRealTimePreviewProps> = ({
currentFormData,
existingClassifications,
isEdit = false,
editingClassificationId,
}) => {
const [isExpanded, setIsExpanded] = useState(false);
// 生成预览用的分类列表
const previewClassifications = useMemo(() => {
let classifications = [...existingClassifications];
if (isEdit && editingClassificationId) {
// 编辑模式:替换现有分类
const index = classifications.findIndex(c => c.id === editingClassificationId);
if (index !== -1) {
classifications[index] = {
...classifications[index],
name: currentFormData.name,
prompt_text: currentFormData.prompt_text,
description: currentFormData.description || undefined,
sort_order: currentFormData.sort_order,
};
}
} else {
// 创建模式:添加新分类
if (currentFormData.name.trim() && currentFormData.prompt_text.trim()) {
const newClassification: AiClassification = {
id: 'preview-new',
name: currentFormData.name,
prompt_text: currentFormData.prompt_text,
description: currentFormData.description || undefined,
is_active: true,
sort_order: currentFormData.sort_order,
weight: currentFormData.weight,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
classifications.push(newClassification);
}
}
// 只返回激活的分类,并按排序顺序排列
return classifications
.filter(c => c.is_active)
.sort((a, b) => a.sort_order - b.sort_order);
}, [currentFormData, existingClassifications, isEdit, editingClassificationId]);
// 生成完整提示词
const fullPrompt = useMemo(() => {
if (previewClassifications.length === 0) {
return '暂无激活的分类,无法生成提示词';
}
// 生成分类名称列表
const categoryNames = previewClassifications
.map(c => c.name)
.join('\n - ');
// 生成详细分类描述
const categoriesStr = previewClassifications
.map(c => `**${c.name}**: ${c.prompt_text}`)
.join('\n - ');
return `请分析这个视频的内容,并将其分类到以下类别之一:
- ${categoryNames}
请按以下步骤进行分析:
1. **冲突处理**
- 按最大可见面积分类
2. **内容分类**(仅对包含目标商品且质量合格的视频):
- ${categoriesStr}
请返回JSON格式的结果
{
"category": "分类结果",
"confidence": 0.85,
"reasoning": "详细的分类理由,包括商品匹配情况和内容特征",
"features": ["观察到的关键特征1", "关键特征2", "关键特征3"],
"product_match": true/false,
"quality_score": 0.9
}
**分类优先级**
1. 商品匹配 > 内容分类
2. 质量合格 > 内容丰富
3. 明确分类 > 模糊归类
请仔细观察视频内容,确保分类准确性。`;
}, [previewClassifications]);
// 检查是否有变化
const hasChanges = useMemo(() => {
return currentFormData.name.trim() !== '' || currentFormData.prompt_text.trim() !== '';
}, [currentFormData]);
return (
<div className="border border-gray-200 rounded-lg overflow-hidden">
{/* 预览标题栏 */}
<div
className="bg-gray-50 px-4 py-3 cursor-pointer hover:bg-gray-100 transition-colors"
onClick={() => setIsExpanded(!isExpanded)}
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
{isExpanded ? (
<EyeIcon className="h-4 w-4 text-gray-500" />
) : (
<EyeSlashIcon className="h-4 w-4 text-gray-500" />
)}
<h4 className="text-sm font-medium text-gray-900">
</h4>
{hasChanges && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">
</span>
)}
</div>
<div className="text-xs text-gray-500">
{previewClassifications.length}
</div>
</div>
</div>
{/* 预览内容 */}
{isExpanded && (
<div className="p-4 space-y-4">
{/* 分类列表预览 */}
<div>
<h5 className="text-xs font-medium text-gray-700 mb-2">
({previewClassifications.length})
</h5>
<div className="space-y-1">
{previewClassifications.map((classification, index) => (
<div
key={classification.id}
className={`text-xs p-2 rounded ${
classification.id === 'preview-new'
? 'bg-green-50 border border-green-200'
: classification.id === editingClassificationId
? 'bg-blue-50 border border-blue-200'
: 'bg-gray-50'
}`}
>
<div className="flex items-center space-x-2">
<span className="font-medium text-gray-900">
{index + 1}. {classification.name}
</span>
{classification.id === 'preview-new' && (
<span className="text-green-600 font-medium">()</span>
)}
{classification.id === editingClassificationId && (
<span className="text-blue-600 font-medium">()</span>
)}
</div>
<div className="text-gray-600 mt-1">
{classification.prompt_text}
</div>
</div>
))}
</div>
</div>
{/* 完整提示词预览 */}
<div>
<h5 className="text-xs font-medium text-gray-700 mb-2">
</h5>
<div className="bg-gray-900 rounded p-3 max-h-48 overflow-y-auto">
<pre className="text-xs text-gray-100 whitespace-pre-wrap font-mono">
{fullPrompt}
</pre>
</div>
<div className="mt-1 text-xs text-gray-500">
: {fullPrompt.length}
</div>
</div>
{/* 提示信息 */}
<div className="bg-blue-50 border border-blue-200 rounded p-3">
<div className="text-xs text-blue-700">
<strong></strong>
<ul className="mt-1 space-y-1">
<li> </li>
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
</div>
)}
</div>
);
};