feat: 在按顺序匹配规则编辑器中集成权重配置功能

This commit is contained in:
imeepos 2025-07-25 17:25:52 +08:00
parent 7a5859e424
commit 68124b92e9
3 changed files with 200 additions and 34 deletions

View File

@ -5,10 +5,12 @@ import { AiClassification } from '../../types/aiClassification';
import { useTemplateStore } from '../../stores/templateStore';
import { CustomSelect } from '../CustomSelect';
import { AiClassificationService } from '../../services/aiClassificationService';
import { TemplateSegmentWeightService } from '../../services/templateSegmentWeightService';
interface SegmentMatchingRuleEditorProps {
segmentId: string;
currentRule: SegmentMatchingRule;
templateId?: string; // 添加模板ID用于权重配置
onRuleUpdated?: (newRule: SegmentMatchingRule) => void;
}
@ -19,6 +21,7 @@ interface SegmentMatchingRuleEditorProps {
export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps> = ({
segmentId,
currentRule,
templateId,
onRuleUpdated,
}) => {
const [isEditing, setIsEditing] = useState(false);
@ -27,6 +30,11 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 权重配置相关状态
const [currentWeights, setCurrentWeights] = useState<Record<string, number>>({});
const [editingWeights, setEditingWeights] = useState<Record<string, number>>({});
const [weightLoading, setWeightLoading] = useState(false);
const { updateSegmentMatchingRule } = useTemplateStore();
// 加载AI分类列表
@ -45,6 +53,31 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
}
}, [isEditing]);
// 加载权重数据
useEffect(() => {
const loadWeightData = async () => {
if (!templateId) return;
try {
setWeightLoading(true);
const weights = await TemplateSegmentWeightService.getSegmentWeightsWithDefaults(
templateId,
segmentId
);
setCurrentWeights(weights);
setEditingWeights({ ...weights });
} catch (error) {
console.error('Failed to load weight data:', error);
} finally {
setWeightLoading(false);
}
};
if (isEditing && templateId && SegmentMatchingRuleHelper.isPriorityOrder(editingRule)) {
loadWeightData();
}
}, [isEditing, templateId, segmentId, editingRule]);
// 重置编辑状态
useEffect(() => {
setEditingRule(currentRule);
@ -68,8 +101,24 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
setLoading(true);
setError(null);
// 保存匹配规则
await updateSegmentMatchingRule(segmentId, editingRule);
// 如果是按顺序匹配规则且有模板ID同时保存权重配置
if (SegmentMatchingRuleHelper.isPriorityOrder(editingRule) && templateId) {
try {
await TemplateSegmentWeightService.setSegmentWeights(
templateId,
segmentId,
editingWeights
);
setCurrentWeights({ ...editingWeights });
} catch (weightError) {
console.error('保存权重配置失败:', weightError);
// 权重保存失败不阻止规则保存
}
}
setIsEditing(false);
onRuleUpdated?.(editingRule);
} catch (error) {
@ -128,6 +177,25 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
}
};
// 权重编辑处理函数
const handleWeightChange = (classificationId: string, weight: number) => {
setEditingWeights(prev => ({
...prev,
[classificationId]: weight,
}));
};
const validateWeight = (weight: number): boolean => {
return weight >= 0 && weight <= 100;
};
const getWeightDisplayInfo = (weight: number) => {
if (weight >= 80) return { text: '高', colorClass: 'text-green-600' };
if (weight >= 60) return { text: '中', colorClass: 'text-yellow-600' };
if (weight >= 40) return { text: '低', colorClass: 'text-orange-600' };
return { text: '极低', colorClass: 'text-red-600' };
};
const getCurrentRuleType = (rule: SegmentMatchingRule): string => {
if (SegmentMatchingRuleHelper.isFixedMaterial(rule)) {
return 'fixed';
@ -279,6 +347,86 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
AI分类
</div>
)}
{/* 权重配置区域 - 只在有模板ID且选择了分类时显示 */}
{templateId && typeof editingRule === 'object' && 'PriorityOrder' in editingRule && editingRule.PriorityOrder.category_ids.length > 0 && (
<div className="mt-4 pt-3 border-t border-blue-200">
<div className="flex items-center justify-between mb-2">
<label className="block text-xs font-medium text-blue-800">
</label>
{weightLoading && (
<div className="text-xs text-blue-600 flex items-center">
<div className="animate-spin rounded-full h-3 w-3 border-b-2 border-blue-600 mr-1"></div>
...
</div>
)}
</div>
<div className="text-xs text-gray-600 mb-3">
</div>
<div className="space-y-2 max-h-40 overflow-y-auto">
{aiClassifications
.filter(classification =>
typeof editingRule === 'object' && 'PriorityOrder' in editingRule &&
editingRule.PriorityOrder.category_ids.includes(classification.id)
)
.sort((a, b) => (editingWeights[b.id] || b.weight || 0) - (editingWeights[a.id] || a.weight || 0))
.map((classification) => {
const currentWeight = editingWeights[classification.id] || classification.weight || 0;
const displayInfo = getWeightDisplayInfo(currentWeight);
return (
<div key={classification.id} className="bg-gray-50 border border-gray-200 rounded-md p-2">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-medium text-gray-700">
{classification.name}
</span>
<div className="flex items-center space-x-2">
<span className={`text-xs font-medium ${displayInfo.colorClass}`}>
{displayInfo.text}
</span>
<span className="text-xs text-gray-500 bg-white px-2 py-1 rounded border">
{currentWeight}
</span>
</div>
</div>
<div className="flex items-center space-x-2">
<input
type="range"
min="0"
max="100"
step="5"
value={currentWeight}
onChange={(e) => handleWeightChange(classification.id, parseInt(e.target.value))}
className="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider"
/>
<input
type="number"
min="0"
max="100"
value={currentWeight}
onChange={(e) => {
const value = parseInt(e.target.value) || 0;
if (validateWeight(value)) {
handleWeightChange(classification.id, value);
}
}}
className="w-12 text-xs text-center border border-gray-300 rounded px-1 py-1"
/>
</div>
</div>
);
})}
</div>
<div className="mt-3 text-xs text-gray-500 bg-blue-50 border border-blue-200 p-2 rounded-md">
💡 0-100
</div>
</div>
)}
</div>
)}
</div>

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { X, Calendar, Clock, Monitor, Layers, FileText, Image, Video, Music, Type, Sparkles, CheckCircle, XCircle, AlertCircle, Upload, Cloud, ChevronDown, ChevronRight, Info } from 'lucide-react';
import { Template, TemplateMaterial, TemplateMaterialType, TrackType } from '../../types/template';
import { Template, TemplateMaterial, TemplateMaterialType, TrackType, SegmentMatchingRuleHelper } from '../../types/template';
import { SegmentMatchingRuleEditor } from './SegmentMatchingRuleEditor';
import { SegmentWeightIndicator, WeightPreviewTooltip } from './SegmentWeightIndicator';
import { TemplateSegmentWeightEditor } from './TemplateSegmentWeightEditor';
@ -88,16 +88,18 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
};
const handleBatchWeightConfig = () => {
// 收集所有片段信息
const allSegments = currentTemplate.tracks.flatMap(track =>
track.segments.map(segment => ({
templateId: currentTemplate.id,
trackSegmentId: segment.id,
segmentName: `${track.name} - ${segment.name}`,
}))
// 只收集使用"按顺序匹配"规则的片段信息
const priorityOrderSegments = currentTemplate.tracks.flatMap(track =>
track.segments
.filter(segment => SegmentMatchingRuleHelper.isPriorityOrder(segment.matching_rule))
.map(segment => ({
templateId: currentTemplate.id,
trackSegmentId: segment.id,
segmentName: `${track.name} - ${segment.name}`,
}))
);
setSelectedSegments(allSegments);
setSelectedSegments(priorityOrderSegments);
setShowBatchWeightModal(true);
};
@ -106,6 +108,11 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
handleWeightsUpdated();
};
// 检查是否有使用"按顺序匹配"规则的片段
const hasPriorityOrderSegments = currentTemplate.tracks.some(track =>
track.segments.some(segment => SegmentMatchingRuleHelper.isPriorityOrder(segment.matching_rule))
);
// 格式化时长
const formatDuration = (microseconds: number) => {
const seconds = Math.floor(microseconds / 1000000);
@ -429,7 +436,13 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
{currentTemplate.tracks.reduce((total, track) => total + track.segments.length, 0)}
</div>
<div className="text-sm text-gray-600 mt-1"></div>
<div className="text-xs text-gray-500"></div>
<div className="text-xs text-gray-500">
{currentTemplate.tracks.reduce((total, track) =>
total + track.segments.filter(segment =>
SegmentMatchingRuleHelper.isPriorityOrder(segment.matching_rule)
).length, 0
)}
</div>
</div>
</div>
</div>
@ -676,13 +689,15 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
{currentTemplate.tracks.length}
</span>
</div>
<button
onClick={handleBatchWeightConfig}
className="inline-flex items-center px-3 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 transition-colors"
>
<Sparkles className="w-4 h-4 mr-2" />
</button>
{hasPriorityOrderSegments && (
<button
onClick={handleBatchWeightConfig}
className="inline-flex items-center px-3 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 transition-colors"
>
<Sparkles className="w-4 h-4 mr-2" />
</button>
)}
</div>
<div className="space-y-4">
@ -770,21 +785,23 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
)}
</div>
{/* 权重配置指示器 */}
<div className="mt-2">
<WeightPreviewTooltip
templateId={currentTemplate.id}
trackSegmentId={segment.id}
>
<SegmentWeightIndicator
{/* 权重配置指示器 - 只在按顺序匹配时显示 */}
{SegmentMatchingRuleHelper.isPriorityOrder(segment.matching_rule) && (
<div className="mt-2">
<WeightPreviewTooltip
templateId={currentTemplate.id}
trackSegmentId={segment.id}
onEditClick={() => toggleWeightEditor(segment.id)}
onWeightsUpdated={handleWeightsUpdated}
className="inline-block"
/>
</WeightPreviewTooltip>
</div>
>
<SegmentWeightIndicator
templateId={currentTemplate.id}
trackSegmentId={segment.id}
onEditClick={() => toggleWeightEditor(segment.id)}
onWeightsUpdated={handleWeightsUpdated}
className="inline-block"
/>
</WeightPreviewTooltip>
</div>
)}
</div>
<button
@ -804,6 +821,7 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
<SegmentMatchingRuleEditor
segmentId={segment.id}
currentRule={segment.matching_rule}
templateId={currentTemplate.id}
onRuleUpdated={(newRule) => handleRuleUpdated(segment.id, newRule)}
/>
</div>
@ -817,8 +835,8 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
</code>
</div>
)}
{/* 权重配置编辑器 */}
{editingWeights[segment.id] && (
{/* 权重配置编辑器 - 只在按顺序匹配时显示 */}
{editingWeights[segment.id] && SegmentMatchingRuleHelper.isPriorityOrder(segment.matching_rule) && (
<div className="mt-4 pt-4 border-t border-gray-100">
<TemplateSegmentWeightEditor
templateId={currentTemplate.id}

View File

@ -5,7 +5,7 @@ import {
UpdateTemplateSegmentWeightRequest,
BatchUpdateTemplateSegmentWeightRequest,
} from '../types/template';
import { AiClassification } from '../types/ai-classification';
import { AiClassification } from '../types/aiClassification';
/**
*