From 73c2187757eba39b111b2cc1ca52f2295a6f9a88 Mon Sep 17 00:00:00 2001 From: imeepos Date: Tue, 15 Jul 2025 10:19:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0[=E9=9A=8F=E6=9C=BA?= =?UTF-8?q?=E5=8C=B9=E9=85=8D]=E9=80=89=E9=A1=B9=E5=88=B0TemplateSegment?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在Rust后端SegmentMatchingRule枚举中添加RandomMatch变体 - 更新TypeScript前端类型定义和SegmentMatchingRuleHelper工具函数 - 修改SegmentMatchingRuleEditor组件支持随机匹配选项 - 添加绿色样式标识随机匹配规则 - 添加完整的单元测试覆盖新功能 遵循promptx/tauri-desktop-app-expert开发规范 --- .../src-tauri/src/data/models/template.rs | 8 ++ .../template/SegmentMatchingRuleEditor.tsx | 18 ++- .../src/types/__tests__/template.test.ts | 130 ++++++++++++++++++ apps/desktop/src/types/template.ts | 19 ++- 4 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/src/types/__tests__/template.test.ts diff --git a/apps/desktop/src-tauri/src/data/models/template.rs b/apps/desktop/src-tauri/src/data/models/template.rs index 61e2a95..50544c8 100644 --- a/apps/desktop/src-tauri/src/data/models/template.rs +++ b/apps/desktop/src-tauri/src/data/models/template.rs @@ -70,6 +70,8 @@ pub enum SegmentMatchingRule { FixedMaterial, /// AI分类素材 - 使用指定AI分类的素材 AiClassification { category_id: String, category_name: String }, + /// 随机匹配 - 从项目中随机选择合适的素材 + RandomMatch, } impl Default for SegmentMatchingRule { @@ -84,6 +86,7 @@ impl SegmentMatchingRule { match self { Self::FixedMaterial => "固定素材".to_string(), Self::AiClassification { category_name, .. } => format!("AI分类: {}", category_name), + Self::RandomMatch => "随机匹配".to_string(), } } @@ -96,6 +99,11 @@ impl SegmentMatchingRule { pub fn is_ai_classification(&self) -> bool { matches!(self, Self::AiClassification { .. }) } + + /// 检查是否为随机匹配 + pub fn is_random_match(&self) -> bool { + matches!(self, Self::RandomMatch) + } } /// 轨道片段 diff --git a/apps/desktop/src/components/template/SegmentMatchingRuleEditor.tsx b/apps/desktop/src/components/template/SegmentMatchingRuleEditor.tsx index 9b6b5c4..4f67d74 100644 --- a/apps/desktop/src/components/template/SegmentMatchingRuleEditor.tsx +++ b/apps/desktop/src/components/template/SegmentMatchingRuleEditor.tsx @@ -92,6 +92,8 @@ export const SegmentMatchingRuleEditor: React.FC firstClassification.name )); } + } else if (ruleType === 'random') { + setEditingRule(SegmentMatchingRuleHelper.createRandomMatch()); } }; @@ -106,12 +108,20 @@ export const SegmentMatchingRuleEditor: React.FC }; const getCurrentRuleType = (rule: SegmentMatchingRule): string => { - return SegmentMatchingRuleHelper.isFixedMaterial(rule) ? 'fixed' : 'ai_classification'; + if (SegmentMatchingRuleHelper.isFixedMaterial(rule)) { + return 'fixed'; + } else if (SegmentMatchingRuleHelper.isAiClassification(rule)) { + return 'ai_classification'; + } else if (SegmentMatchingRuleHelper.isRandomMatch(rule)) { + return 'random'; + } + return 'fixed'; // 默认值 }; const ruleTypeOptions = [ { value: 'fixed', label: '固定素材' }, { value: 'ai_classification', label: 'AI分类素材' }, + { value: 'random', label: '随机匹配' }, ]; const classificationOptions = aiClassifications.map(classification => ({ @@ -127,7 +137,11 @@ export const SegmentMatchingRuleEditor: React.FC {SegmentMatchingRuleHelper.getDisplayName(currentRule)} diff --git a/apps/desktop/src/types/__tests__/template.test.ts b/apps/desktop/src/types/__tests__/template.test.ts new file mode 100644 index 0000000..f329fde --- /dev/null +++ b/apps/desktop/src/types/__tests__/template.test.ts @@ -0,0 +1,130 @@ +import { describe, it, expect } from 'vitest'; +import { SegmentMatchingRuleHelper, SegmentMatchingRule } from '../template'; + +describe('SegmentMatchingRuleHelper', () => { + describe('createFixedMaterial', () => { + it('should create a fixed material rule', () => { + const rule = SegmentMatchingRuleHelper.createFixedMaterial(); + expect(rule).toBe('FixedMaterial'); + }); + }); + + describe('createAiClassification', () => { + it('should create an AI classification rule', () => { + const rule = SegmentMatchingRuleHelper.createAiClassification('test-id', 'Test Category'); + expect(rule).toEqual({ + AiClassification: { + category_id: 'test-id', + category_name: 'Test Category' + } + }); + }); + }); + + describe('createRandomMatch', () => { + it('should create a random match rule', () => { + const rule = SegmentMatchingRuleHelper.createRandomMatch(); + expect(rule).toBe('RandomMatch'); + }); + }); + + describe('getDisplayName', () => { + it('should return correct display name for fixed material', () => { + const rule = SegmentMatchingRuleHelper.createFixedMaterial(); + const displayName = SegmentMatchingRuleHelper.getDisplayName(rule); + expect(displayName).toBe('固定素材'); + }); + + it('should return correct display name for AI classification', () => { + const rule = SegmentMatchingRuleHelper.createAiClassification('test-id', 'Test Category'); + const displayName = SegmentMatchingRuleHelper.getDisplayName(rule); + expect(displayName).toBe('AI分类: Test Category'); + }); + + it('should return correct display name for random match', () => { + const rule = SegmentMatchingRuleHelper.createRandomMatch(); + const displayName = SegmentMatchingRuleHelper.getDisplayName(rule); + expect(displayName).toBe('随机匹配'); + }); + + it('should return unknown rule for invalid rule', () => { + const rule = {} as SegmentMatchingRule; + const displayName = SegmentMatchingRuleHelper.getDisplayName(rule); + expect(displayName).toBe('未知规则'); + }); + }); + + describe('isFixedMaterial', () => { + it('should return true for fixed material rule', () => { + const rule = SegmentMatchingRuleHelper.createFixedMaterial(); + expect(SegmentMatchingRuleHelper.isFixedMaterial(rule)).toBe(true); + }); + + it('should return false for AI classification rule', () => { + const rule = SegmentMatchingRuleHelper.createAiClassification('test-id', 'Test Category'); + expect(SegmentMatchingRuleHelper.isFixedMaterial(rule)).toBe(false); + }); + + it('should return false for random match rule', () => { + const rule = SegmentMatchingRuleHelper.createRandomMatch(); + expect(SegmentMatchingRuleHelper.isFixedMaterial(rule)).toBe(false); + }); + }); + + describe('isAiClassification', () => { + it('should return false for fixed material rule', () => { + const rule = SegmentMatchingRuleHelper.createFixedMaterial(); + expect(SegmentMatchingRuleHelper.isAiClassification(rule)).toBe(false); + }); + + it('should return true for AI classification rule', () => { + const rule = SegmentMatchingRuleHelper.createAiClassification('test-id', 'Test Category'); + expect(SegmentMatchingRuleHelper.isAiClassification(rule)).toBe(true); + }); + + it('should return false for random match rule', () => { + const rule = SegmentMatchingRuleHelper.createRandomMatch(); + expect(SegmentMatchingRuleHelper.isAiClassification(rule)).toBe(false); + }); + }); + + describe('isRandomMatch', () => { + it('should return false for fixed material rule', () => { + const rule = SegmentMatchingRuleHelper.createFixedMaterial(); + expect(SegmentMatchingRuleHelper.isRandomMatch(rule)).toBe(false); + }); + + it('should return false for AI classification rule', () => { + const rule = SegmentMatchingRuleHelper.createAiClassification('test-id', 'Test Category'); + expect(SegmentMatchingRuleHelper.isRandomMatch(rule)).toBe(false); + }); + + it('should return true for random match rule', () => { + const rule = SegmentMatchingRuleHelper.createRandomMatch(); + expect(SegmentMatchingRuleHelper.isRandomMatch(rule)).toBe(true); + }); + }); + + describe('getAiClassificationInfo', () => { + it('should return null for fixed material rule', () => { + const rule = SegmentMatchingRuleHelper.createFixedMaterial(); + const info = SegmentMatchingRuleHelper.getAiClassificationInfo(rule); + expect(info).toBeNull(); + }); + + it('should return classification info for AI classification rule', () => { + const rule = SegmentMatchingRuleHelper.createAiClassification('test-id', 'Test Category'); + const info = SegmentMatchingRuleHelper.getAiClassificationInfo(rule); + expect(info).toEqual({ + category_id: 'test-id', + category_name: 'Test Category' + }); + }); + + it('should return null for random match rule', () => { + const rule = SegmentMatchingRuleHelper.createRandomMatch(); + const info = SegmentMatchingRuleHelper.getAiClassificationInfo(rule); + expect(info).toBeNull(); + }); + }); +}); diff --git a/apps/desktop/src/types/template.ts b/apps/desktop/src/types/template.ts index 3e2a6fe..9a7e215 100644 --- a/apps/desktop/src/types/template.ts +++ b/apps/desktop/src/types/template.ts @@ -58,7 +58,8 @@ export interface Track { */ export type SegmentMatchingRule = | "FixedMaterial" - | { AiClassification: { category_id: string; category_name: string } }; + | { AiClassification: { category_id: string; category_name: string } } + | "RandomMatch"; /** * 片段匹配规则辅助函数 @@ -78,6 +79,13 @@ export const SegmentMatchingRuleHelper = { return { AiClassification: { category_id: categoryId, category_name: categoryName } }; }, + /** + * 创建随机匹配规则 + */ + createRandomMatch(): SegmentMatchingRule { + return "RandomMatch"; + }, + /** * 获取规则的显示名称 */ @@ -86,6 +94,8 @@ export const SegmentMatchingRuleHelper = { return '固定素材'; } else if (typeof rule === 'object' && 'AiClassification' in rule) { return `AI分类: ${rule.AiClassification.category_name}`; + } else if (rule === "RandomMatch") { + return '随机匹配'; } return '未知规则'; }, @@ -104,6 +114,13 @@ export const SegmentMatchingRuleHelper = { return typeof rule === 'object' && 'AiClassification' in rule; }, + /** + * 检查是否为随机匹配 + */ + isRandomMatch(rule: SegmentMatchingRule): boolean { + return rule === "RandomMatch"; + }, + /** * 获取AI分类信息 */