feat: 实现AI分类设置功能 (v0.1.7)
新增功能: - AI分类CRUD操作 (创建、读取、更新、删除) - 实时提示词预览功能 - 分类排序和状态管理 - 完整的表单验证和错误处理 后端架构: - 数据层: AiClassification模型和仓储 - 业务层: AiClassificationService业务逻辑 - 表示层: 10个Tauri命令接口 - 数据库: ai_classifications表和索引 前端架构: - 类型系统: 完整的TypeScript类型定义 - 服务层: AiClassificationService API封装 - 组件层: 5个专用组件 (主页面、表单、预览、删除确认、实时预览) - 路由集成: /ai-classification-settings 质量保证: - 52个单元测试 (100%通过) - TypeScript和Rust编译无错误 - 遵循promptx开发规范 核心特性: - 支持分类名称和提示词定义 - 实时生成完整AI分类提示词 - 拖拽排序和批量操作 - 优雅的用户界面和交互体验
This commit is contained in:
220
apps/desktop/src/components/AiClassificationRealTimePreview.tsx
Normal file
220
apps/desktop/src/components/AiClassificationRealTimePreview.tsx
Normal file
@@ -0,0 +1,220 @@
|
||||
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,
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user