From 68124b92e90ac2c9e655079077c08b93c73f49f0 Mon Sep 17 00:00:00 2001 From: imeepos Date: Fri, 25 Jul 2025 17:25:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=A8=E6=8C=89=E9=A1=BA=E5=BA=8F?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E8=A7=84=E5=88=99=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E4=B8=AD=E9=9B=86=E6=88=90=E6=9D=83=E9=87=8D=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/SegmentMatchingRuleEditor.tsx | 150 +++++++++++++++++- .../template/TemplateDetailModal.tsx | 82 ++++++---- .../services/templateSegmentWeightService.ts | 2 +- 3 files changed, 200 insertions(+), 34 deletions(-) diff --git a/apps/desktop/src/components/template/SegmentMatchingRuleEditor.tsx b/apps/desktop/src/components/template/SegmentMatchingRuleEditor.tsx index 20df928..3ee23ff 100644 --- a/apps/desktop/src/components/template/SegmentMatchingRuleEditor.tsx +++ b/apps/desktop/src/components/template/SegmentMatchingRuleEditor.tsx @@ -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 = ({ segmentId, currentRule, + templateId, onRuleUpdated, }) => { const [isEditing, setIsEditing] = useState(false); @@ -27,6 +30,11 @@ export const SegmentMatchingRuleEditor: React.FC const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + // 权重配置相关状态 + const [currentWeights, setCurrentWeights] = useState>({}); + const [editingWeights, setEditingWeights] = useState>({}); + const [weightLoading, setWeightLoading] = useState(false); + const { updateSegmentMatchingRule } = useTemplateStore(); // 加载AI分类列表 @@ -45,6 +53,31 @@ export const SegmentMatchingRuleEditor: React.FC } }, [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 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 } }; + // 权重编辑处理函数 + 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 ⚠️ 请至少选择一个AI分类 )} + + {/* 权重配置区域 - 只在有模板ID且选择了分类时显示 */} + {templateId && typeof editingRule === 'object' && 'PriorityOrder' in editingRule && editingRule.PriorityOrder.category_ids.length > 0 && ( +
+
+ + {weightLoading && ( +
+
+ 加载中... +
+ )} +
+
+ 调整各分类的权重值,权重越高的分类优先匹配 +
+ +
+ {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 ( +
+
+ + {classification.name} + +
+ + {displayInfo.text} + + + {currentWeight} + +
+
+ +
+ handleWeightChange(classification.id, parseInt(e.target.value))} + className="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider" + /> + { + 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" + /> +
+
+ ); + })} +
+ +
+ 💡 权重范围:0-100,数值越高优先级越高。保存规则时会同时保存权重配置。 +
+
+ )} )} diff --git a/apps/desktop/src/components/template/TemplateDetailModal.tsx b/apps/desktop/src/components/template/TemplateDetailModal.tsx index 3119c4c..e9c474c 100644 --- a/apps/desktop/src/components/template/TemplateDetailModal.tsx +++ b/apps/desktop/src/components/template/TemplateDetailModal.tsx @@ -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 = ({ }; 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 = ({ 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 = ({ {currentTemplate.tracks.reduce((total, track) => total + track.segments.length, 0)}
片段
-
权重可配置
+
+ {currentTemplate.tracks.reduce((total, track) => + total + track.segments.filter(segment => + SegmentMatchingRuleHelper.isPriorityOrder(segment.matching_rule) + ).length, 0 + )} 个可配置权重 +
@@ -676,13 +689,15 @@ export const TemplateDetailModal: React.FC = ({ {currentTemplate.tracks.length} 个轨道 - + {hasPriorityOrderSegments && ( + + )}
@@ -770,21 +785,23 @@ export const TemplateDetailModal: React.FC = ({ )}
- {/* 权重配置指示器 */} -
- - + toggleWeightEditor(segment.id)} - onWeightsUpdated={handleWeightsUpdated} - className="inline-block" - /> - -
+ > + toggleWeightEditor(segment.id)} + onWeightsUpdated={handleWeightsUpdated} + className="inline-block" + /> + + + )}