mixvideo-v2/apps/desktop/src/components/AiClassificationRealTimePre...

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>
);
};