feat: 实现模板匹配按顺序匹配功能

新功能:
- 添加AI分类权重字段,支持按权重顺序匹配
- 新增PriorityOrder匹配规则类型
- 实现按权重顺序的素材匹配算法
- 添加权重编辑器UI组件

数据模型扩展:
- AiClassification模型添加weight字段
- SegmentMatchingRule枚举添加PriorityOrder类型
- 扩展相关的请求和响应类型定义

数据库迁移:
- 创建019迁移脚本为ai_classifications表添加weight字段
- 为现有数据设置默认权重值
- 添加权重索引提高查询性能

后端服务实现:
- MaterialMatchingService支持按顺序匹配逻辑
- AiClassificationService添加按权重获取分类方法
- 更新所有相关的构造函数和命令处理

前端UI优化:
- SegmentMatchingRuleEditor支持按顺序匹配配置
- 新增WeightEditor组件用于权重设置
- AI分类设置页面集成权重编辑功能
- 更新TypeScript类型定义

测试验证:
- 添加完整的单元测试套件
- 6个测试用例全部通过
- 验证权重排序和匹配规则逻辑

遵循promptx/tauri-desktop-app-expert开发规范
支持用户自定义分类权重,实现智能按顺序匹配
This commit is contained in:
imeepos
2025-07-23 20:28:36 +08:00
parent de620447b4
commit 0cfacd0662
17 changed files with 628 additions and 9 deletions

View File

@@ -94,6 +94,10 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
}
} else if (ruleType === 'random') {
setEditingRule(SegmentMatchingRuleHelper.createRandomMatch());
} else if (ruleType === 'priority_order') {
// 默认选择所有激活的AI分类
const categoryIds = aiClassifications.map(c => c.id);
setEditingRule(SegmentMatchingRuleHelper.createPriorityOrder(categoryIds));
}
};
@@ -107,6 +111,23 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
}
};
const handlePriorityOrderChange = (categoryId: string, isSelected: boolean) => {
if (typeof editingRule === 'object' && 'PriorityOrder' in editingRule) {
const currentCategoryIds = editingRule.PriorityOrder.category_ids;
let newCategoryIds: string[];
if (isSelected) {
// 添加分类ID
newCategoryIds = [...currentCategoryIds, categoryId];
} else {
// 移除分类ID
newCategoryIds = currentCategoryIds.filter(id => id !== categoryId);
}
setEditingRule(SegmentMatchingRuleHelper.createPriorityOrder(newCategoryIds));
}
};
const getCurrentRuleType = (rule: SegmentMatchingRule): string => {
if (SegmentMatchingRuleHelper.isFixedMaterial(rule)) {
return 'fixed';
@@ -114,6 +135,8 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
return 'ai_classification';
} else if (SegmentMatchingRuleHelper.isRandomMatch(rule)) {
return 'random';
} else if (SegmentMatchingRuleHelper.isPriorityOrder(rule)) {
return 'priority_order';
}
return 'fixed'; // 默认值
};
@@ -122,6 +145,7 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
{ value: 'fixed', label: '固定素材' },
{ value: 'ai_classification', label: 'AI分类素材' },
{ value: 'random', label: '随机匹配' },
{ value: 'priority_order', label: '按顺序匹配' },
];
const classificationOptions = aiClassifications.map(classification => ({
@@ -141,6 +165,8 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
? 'bg-blue-100 text-blue-800'
: SegmentMatchingRuleHelper.isRandomMatch(currentRule)
? 'bg-green-100 text-green-800'
: SegmentMatchingRuleHelper.isPriorityOrder(currentRule)
? 'bg-purple-100 text-purple-800'
: 'bg-gray-100 text-gray-800'
}`}>
{SegmentMatchingRuleHelper.getDisplayName(currentRule)}
@@ -212,6 +238,49 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
/>
</div>
)}
{SegmentMatchingRuleHelper.isPriorityOrder(editingRule) && (
<div>
<label className="block text-xs font-medium text-blue-800 mb-1">
</label>
<div className="text-xs text-gray-600 mb-2">
AI分类的权重顺序依次尝试匹配素材
</div>
<div className="space-y-1 max-h-32 overflow-y-auto border border-blue-200 rounded-md p-2 bg-white">
{aiClassifications
.sort((a, b) => (b.weight || 0) - (a.weight || 0)) // 按权重降序排列
.map((classification) => {
const currentCategoryIds = typeof editingRule === 'object' && 'PriorityOrder' in editingRule
? editingRule.PriorityOrder.category_ids
: [];
const isSelected = currentCategoryIds.includes(classification.id);
return (
<label key={classification.id} className="flex items-center space-x-2 cursor-pointer hover:bg-blue-50 p-1 rounded">
<input
type="checkbox"
checked={isSelected}
onChange={(e) => handlePriorityOrderChange(classification.id, e.target.checked)}
className="w-3 h-3 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<span className="text-xs text-gray-700 flex-1">
{classification.name}
</span>
<span className="text-xs text-gray-500 bg-gray-100 px-1 rounded">
: {classification.weight || 0}
</span>
</label>
);
})}
</div>
{typeof editingRule === 'object' && 'PriorityOrder' in editingRule && editingRule.PriorityOrder.category_ids.length === 0 && (
<div className="text-xs text-amber-700 bg-amber-100 border border-amber-200 p-2 rounded-md mt-2">
AI分类
</div>
)}
</div>
)}
</div>
{error && (