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:
imeepos
2025-07-14 11:39:44 +08:00
parent a5072d4c0f
commit ca63da30ff
24 changed files with 3755 additions and 7 deletions

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