feat: 在按顺序匹配规则编辑器中集成权重配置功能
This commit is contained in:
parent
7a5859e424
commit
68124b92e9
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
UpdateTemplateSegmentWeightRequest,
|
||||
BatchUpdateTemplateSegmentWeightRequest,
|
||||
} from '../types/template';
|
||||
import { AiClassification } from '../types/ai-classification';
|
||||
import { AiClassification } from '../types/aiClassification';
|
||||
|
||||
/**
|
||||
* 模板片段权重配置服务
|
||||
|
|
|
|||
Loading…
Reference in New Issue