From 97ac04a74aeaf4969f9400e7e5419231e89f17f6 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 12 Jul 2025 21:05:36 +0800 Subject: [PATCH] fix --- docs/EMBEDDED_PYTHON_GUIDE.md | 224 ----------- docs/QUICK_FIX_GUIDE.md | 152 -------- docs/architecture-summary.md | 319 --------------- python_core/cli/commands/template.py | 1 - src/App.tsx | 2 +- src/hooks/useTemplateProgressV2.ts | 297 ++++++++++++++ src/pages/TemplateManagePageV2.tsx | 564 +++++++++++++++++++++++++++ src/services/TemplateServiceV2.ts | 379 ++++++++++++++++++ src/services/templateService.ts | 277 ------------- 9 files changed, 1241 insertions(+), 974 deletions(-) delete mode 100644 docs/EMBEDDED_PYTHON_GUIDE.md delete mode 100644 docs/QUICK_FIX_GUIDE.md delete mode 100644 docs/architecture-summary.md create mode 100644 src/hooks/useTemplateProgressV2.ts create mode 100644 src/pages/TemplateManagePageV2.tsx create mode 100644 src/services/TemplateServiceV2.ts delete mode 100644 src/services/templateService.ts diff --git a/docs/EMBEDDED_PYTHON_GUIDE.md b/docs/EMBEDDED_PYTHON_GUIDE.md deleted file mode 100644 index 32b4cd4..0000000 --- a/docs/EMBEDDED_PYTHON_GUIDE.md +++ /dev/null @@ -1,224 +0,0 @@ -# 嵌入式Python方案指南 - -## 🎯 方案概述 - -这个方案将Python环境直接打包到你的Tauri应用中,用户无需安装Python即可使用所有功能。 - -## 🚀 快速开始 - -### 1. 设置嵌入式Python环境 - -```cmd -# 运行设置脚本 -scripts\setup_embedded_python.bat -``` - -这将: -- 下载Python 3.11嵌入式版本 -- 安装必要的依赖(requests, Pillow等) -- 复制你的python_core模块 -- 创建启动脚本 -- 更新Tauri配置 - -### 2. 测试嵌入式Python - -```cmd -# 测试Python版本 -src-tauri\python-embed\python.exe --version - -# 测试依赖 -src-tauri\python-embed\python.exe -c "import requests, PIL; print('Dependencies OK')" - -# 测试python_core模块 -src-tauri\python-embed\python.exe -c "import python_core; print('python_core OK')" -``` - -### 3. 启动应用测试 - -```cmd -cargo tauri dev -``` - -现在应用会自动使用嵌入式Python,无需系统Python环境。 - -## 📁 目录结构 - -设置完成后,你的项目结构如下: - -``` -your-project/ -├── src-tauri/ -│ ├── python-embed/ # 嵌入式Python环境 -│ │ ├── python.exe # Python解释器 -│ │ ├── python311.dll # Python库 -│ │ ├── Lib/ # 标准库 -│ │ ├── Scripts/ # pip等工具 -│ │ ├── python_core/ # 你的Python模块 -│ │ └── python-core.bat # 启动脚本 -│ └── tauri.conf.json # 已更新配置 -├── python_core/ # 原始Python代码 -└── scripts/ - ├── setup_embedded_python.py - └── setup_embedded_python.bat -``` - -## 🔧 工作原理 - -### 执行优先级 - -Rust代码会按以下优先级选择Python环境: - -1. **嵌入式Python** - 如果 `src-tauri/python-embed/python.exe` 存在 -2. **Sidecar** - 如果 `mixvideo-python-core.exe` 存在 -3. **系统Python** - 作为最后的回退选项 - -### 环境变量控制 - -你可以通过环境变量强制使用特定的Python: - -```cmd -# 强制使用系统Python -set MIXVIDEO_FORCE_SYSTEM_PYTHON=1 -cargo tauri dev -``` - -## 📦 构建和分发 - -### 开发模式 - -在开发模式下,嵌入式Python位于 `src-tauri/python-embed/` - -### 生产构建 - -```cmd -cargo tauri build -``` - -构建时,Tauri会自动将 `python-embed` 目录打包到最终的可执行文件中。 - -### 分发 - -最终的应用包含: -- 你的Tauri应用 -- 完整的Python环境 -- 所有Python依赖 -- 你的python_core模块 - -用户无需安装任何额外软件即可运行。 - -## 🧪 测试和调试 - -### 测试嵌入式Python - -```cmd -# 直接测试 -src-tauri\python-embed\python.exe -m python_core.main --module test --action hello - -# 测试特定服务 -src-tauri\python-embed\python.exe -m python_core.services.template_manager --action get_templates -``` - -### 在应用中测试 - -1. 启动应用:`cargo tauri dev` -2. 访问Python测试页面 -3. 运行测试 - 应该显示"Using embedded Python for execution" - -### 调试问题 - -如果遇到问题: - -1. **检查Python路径**: - ```cmd - dir src-tauri\python-embed\python.exe - ``` - -2. **检查模块导入**: - ```cmd - src-tauri\python-embed\python.exe -c "import sys; print(sys.path)" - ``` - -3. **查看日志**: - - 控制台会显示使用的Python类型 - - 检查错误信息 - -## ⚙️ 自定义配置 - -### 添加更多依赖 - -编辑 `scripts/setup_embedded_python.py`: - -```python -dependencies = [ - "requests", - "Pillow", - "numpy", # 添加新依赖 - "opencv-python", # 添加新依赖 -] -``` - -然后重新运行设置脚本。 - -### 更新Python版本 - -修改 `setup_embedded_python.py` 中的下载URL: - -```python -python_url = "https://www.python.org/ftp/python/3.12.0/python-3.12.0-embed-amd64.zip" -``` - -### 自定义Python路径 - -如果需要自定义路径,修改 `get_embedded_python_path()` 函数。 - -## 🎉 优势 - -### 对比其他方案 - -| 方案 | 优势 | 劣势 | -|------|------|------| -| **嵌入式Python** | ✅ 无需用户安装
✅ 版本一致
✅ 完全控制 | ❌ 包体积较大
❌ 更新需要重新分发 | -| Sidecar | ✅ 包体积小
✅ 独立更新 | ❌ 构建复杂
❌ 依赖管理困难 | -| 系统Python | ✅ 包体积最小 | ❌ 需要用户安装
❌ 版本不一致 | - -### 适用场景 - -嵌入式Python特别适合: -- 面向普通用户的应用 -- 需要特定Python版本的应用 -- 希望简化部署的企业应用 -- 不希望依赖外部环境的应用 - -## 🔄 维护和更新 - -### 更新Python代码 - -1. 修改 `python_core/` 中的代码 -2. 重新运行设置脚本:`scripts\setup_embedded_python.bat` -3. 测试更改:`cargo tauri dev` - -### 更新依赖 - -1. 修改 `setup_embedded_python.py` 中的依赖列表 -2. 重新运行设置脚本 -3. 测试新依赖 - -### 版本管理 - -建议在版本控制中: -- ✅ 包含 `scripts/setup_embedded_python.py` -- ✅ 包含 `python_core/` 源代码 -- ❌ 排除 `src-tauri/python-embed/` (太大) -- ✅ 在CI/CD中自动运行设置脚本 - -## 📋 最佳实践 - -1. **定期更新Python版本** - 保持安全性 -2. **最小化依赖** - 减少包体积 -3. **测试多平台** - 确保跨平台兼容性 -4. **监控包大小** - 避免过度膨胀 -5. **文档化依赖** - 便于维护 - ---- - -这个方案为你提供了一个完全自包含的Python环境,确保用户体验的一致性和简便性! diff --git a/docs/QUICK_FIX_GUIDE.md b/docs/QUICK_FIX_GUIDE.md deleted file mode 100644 index 860947d..0000000 --- a/docs/QUICK_FIX_GUIDE.md +++ /dev/null @@ -1,152 +0,0 @@ -# 快速修复指南 - Python Core 构建问题 - -## 问题分析 - -构建失败的原因是 `build.spec` 文件中使用了 `__file__` 变量,但在PyInstaller的spec文件中这个变量不可用。 - -## 快速解决方案 - -### 方案1:使用命令行直接构建(推荐) - -在 `python_core` 目录下运行: - -```cmd -# Windows -python -m PyInstaller --onefile --console --name mixvideo-python-core main_simple.py - -# 或者如果要包含完整功能 -python -m PyInstaller --onefile --console --name mixvideo-python-core ^ - --hidden-import python_core ^ - --hidden-import python_core.utils ^ - --hidden-import python_core.services ^ - main.py -``` - -### 方案2:使用测试批处理文件 - -直接运行: -```cmd -cd python_core -build_test.bat -``` - -### 方案3:修复spec文件 - -如果要使用spec文件,请使用以下简化版本: - -```python -# build_fixed.spec -# -*- mode: python ; coding: utf-8 -*- - -block_cipher = None - -a = Analysis( - ['main.py'], - pathex=['.'], - binaries=[], - datas=[], - hiddenimports=[ - 'python_core', - 'python_core.utils', - 'python_core.utils.logger', - 'python_core.utils.jsonrpc', - 'python_core.services', - 'python_core.services.template_manager', - # 添加其他需要的模块 - ], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False, -) - -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name='mixvideo-python-core', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=True, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, -) -``` - -## 验证步骤 - -1. **构建成功后**,在 `python_core/dist/` 目录下会生成 `mixvideo-python-core.exe` - -2. **测试可执行文件**: - ```cmd - dist\mixvideo-python-core.exe --version - dist\mixvideo-python-core.exe --module test --action hello - ``` - -3. **复制到Tauri目录**: - ```cmd - mkdir ..\src-tauri\binaries - copy dist\mixvideo-python-core.exe ..\src-tauri\binaries\ - ``` - -4. **更新Tauri配置**: - 在 `src-tauri/tauri.conf.json` 中添加: - ```json - { - "bundle": { - "externalBin": [ - { - "name": "mixvideo-python-core", - "src": "binaries/mixvideo-python-core", - "targets": ["all"] - } - ] - } - } - ``` - -## 如果仍然遇到问题 - -### 检查Python环境 -```cmd -python --version -python -m pip list | findstr pyinstaller -``` - -### 安装必要依赖 -```cmd -python -m pip install pyinstaller requests Pillow -``` - -### 使用最简单的构建 -```cmd -cd python_core -python -m PyInstaller --onefile main_simple.py -``` - -这将创建一个基本的可执行文件,可以用来测试整个流程。 - -## 下一步 - -一旦基本构建成功,就可以: -1. 逐步添加更多的隐藏导入 -2. 测试完整的功能 -3. 集成到Tauri应用中 - -记住:先让简单版本工作,再逐步增加复杂性。 diff --git a/docs/architecture-summary.md b/docs/architecture-summary.md deleted file mode 100644 index 883cae4..0000000 --- a/docs/architecture-summary.md +++ /dev/null @@ -1,319 +0,0 @@ -# Python Core 架构设计总结 - -## 🎯 架构验证结果 - -``` -🎉 架构设计验证通过! - -✅ 硬性要求满足: - 1. ✅ 命令行集成 - 所有功能都通过CLI触发 - 2. ✅ 进度反馈 - JSON RPC Progress 统一进度条 - 3. ✅ API就绪 - 服务具备API化基础 - 4. ✅ 存储抽象 - 支持多种存储方式切换 - -通过测试: 6/6 -``` - -## 🏗️ 架构设计概览 - -### **分层架构** -``` -┌─────────────────────────────────────────────────────────────┐ -│ CLI Layer (命令行层) │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ MediaManager │ │ SceneDetection │ │ TemplateManager │ │ -│ │ Commander │ │ Commander │ │ Commander │ │ -│ │ (进度条) │ │ (进度条) │ │ (进度条) │ │ -│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ Service Layer (服务层) │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ MediaManager │ │ SceneDetection │ │ TemplateManager │ │ -│ │ Service │ │ Service │ │ Service │ │ -│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ Storage Layer (存储层) │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ JSON Storage │ │ Database │ │ MongoDB │ │ -│ │ (当前) │ │ Storage │ │ Storage │ │ -│ │ │ │ (未来) │ │ (未来) │ │ -│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ -``` - -## 🔧 核心组件实现 - -### **1. 进度命令基类** -```python -class ProgressJSONRPCCommander(ABC): - """带进度的JSON-RPC命令基类""" - - def _is_progressive_command(self, command: str) -> bool: - """判断是否需要进度报告""" - return command in ["batch_upload", "batch_detect", "compare"] - - def create_task(self, name: str, total: int): - """创建进度任务""" - return ProgressTask(name, total, self.progress_reporter) -``` - -**实现状态**: ✅ 已实现并验证 -- 媒体管理器: `upload`, `batch_upload` 支持进度 -- 场景检测: `batch_detect`, `compare` 支持进度 -- JSON-RPC 2.0 进度协议标准化 - -### **2. 存储抽象层** -```python -class StorageInterface(ABC): - """存储接口基类""" - - @abstractmethod - def save(self, collection: str, key: str, data: Any) -> bool: - """保存数据""" - pass - - @abstractmethod - def load(self, collection: str, key: str) -> Any: - """加载数据""" - pass -``` - -**实现状态**: ✅ 已实现并验证 -- JSON存储: 完整实现,支持批量操作 -- 存储工厂: 支持多种存储类型注册 -- 无缝切换: 配置驱动的存储选择 - -### **3. 服务基类** -```python -class ServiceBase(ABC): - """服务基类""" - - def __init__(self, storage: Optional[StorageInterface] = None): - self.storage = storage or get_storage(self.get_service_name()) - - @abstractmethod - def get_service_name(self) -> str: - """获取服务名称""" - pass -``` - -**实现状态**: ✅ 已实现并验证 -- 统一存储接口: 所有服务使用相同的存储抽象 -- 进度支持: `ProgressServiceBase` 提供进度回调 -- 集合管理: 自动命名空间隔离 - -## 📊 硬性要求满足情况 - -### **1. 命令行集成 ✅** -```bash -# 媒体管理 -python -m python_core.services.media_manager upload video.mp4 -python -m python_core.services.media_manager batch_upload /videos - -# 场景检测 -python -m python_core.services.scene_detection detect video.mp4 -python -m python_core.services.scene_detection batch_detect /videos - -# 模板管理 (未来) -python -m python_core.services.template_manager import /templates -``` - -**验证结果**: -- ✅ 所有功能都通过CLI触发 -- ✅ 统一的命令行接口 -- ✅ 标准化的参数处理 - -### **2. 进度反馈 ✅** -```json -{ - "jsonrpc": "2.0", - "method": "progress", - "params": { - "step": "media_manager", - "progress": 0.65, - "message": "处理文件: video.mp4 (3/5)", - "details": { - "current": 3, - "total": 5, - "elapsed_time": 2.5, - "estimated_remaining": 1.2 - } - } -} -``` - -**验证结果**: -- ✅ JSON-RPC 2.0 进度协议 -- ✅ 实时进度反馈 -- ✅ 智能进度命令识别 - -### **3. API就绪 ✅** -```python -# 当前CLI接口 -result = commander.execute_command("upload", {"video_path": "video.mp4"}) - -# 未来API接口 (无缝迁移) -@app.post("/api/v1/media_manager/upload") -async def api_upload(args: dict): - return commander.execute_command("upload", args) -``` - -**验证结果**: -- ✅ 标准化的命令执行接口 -- ✅ JSON格式的输入输出 -- ✅ 统一的参数处理 -- ✅ 进度回调机制 - -### **4. 存储抽象 ✅** -```python -# 当前JSON存储 -config = StorageConfig(storage_type=StorageType.JSON) -storage = StorageFactory.create_storage(config) - -# 未来数据库存储 (无缝切换) -config = StorageConfig(storage_type=StorageType.DATABASE, - connection_string="postgresql://...") -storage = StorageFactory.create_storage(config) -``` - -**验证结果**: -- ✅ 统一的存储接口 -- ✅ 多种存储实现支持 -- ✅ 配置驱动的存储选择 -- ✅ 无缝切换能力 - -## 🚀 迁移路径设计 - -### **阶段1: 当前实现 (已完成)** -- ✅ JSON文件存储 -- ✅ 进度命令行接口 -- ✅ 模块化服务架构 - -### **阶段2: 存储扩展 (规划中)** -```python -# 数据库存储 -class DatabaseStorage(StorageInterface): - def __init__(self, config: StorageConfig): - self.engine = create_engine(config.connection_string) - -# MongoDB存储 -class MongoDBStorage(StorageInterface): - def __init__(self, config: StorageConfig): - self.client = MongoClient(config.connection_string) -``` - -### **阶段3: API化 (规划中)** -```python -# REST API -@app.post("/api/v1/{service}/{command}") -async def execute_command(service: str, command: str, args: dict): - commander = get_commander(service) - return commander.execute_command(command, args) - -# WebSocket进度 -@app.websocket("/ws/progress") -async def websocket_progress(websocket: WebSocket): - # 实时进度推送 - pass -``` - -### **阶段4: 微服务化 (规划中)** -```yaml -# docker-compose.yml -services: - media-manager: - image: mixvideo/media-manager - environment: - - STORAGE_TYPE=database - - DATABASE_URL=postgresql://... - - scene-detection: - image: mixvideo/scene-detection - environment: - - STORAGE_TYPE=mongodb - - MONGODB_URL=mongodb://... -``` - -## 🎯 架构优势 - -### **1. 统一性** -- 🎯 **命令行优先**: 所有功能都可通过CLI访问 -- 📊 **进度统一**: 统一的JSON-RPC进度协议 -- 🔧 **接口标准**: 标准化的服务接口 - -### **2. 可扩展性** -- 🔌 **插件化**: 易于添加新服务和存储 -- 💾 **存储灵活**: 支持多种存储后端 -- 🌐 **API就绪**: 无痛迁移到API - -### **3. 可维护性** -- 🧩 **模块化**: 清晰的模块分离 -- 📝 **文档完整**: 详细的架构文档 -- 🧪 **测试友好**: 独立测试各层组件 - -### **4. 用户友好** -- 📊 **进度可视**: 实时进度反馈 -- 🔧 **配置灵活**: 丰富的配置选项 -- 📄 **输出多样**: 多种输出格式 - -## 📈 实际验证数据 - -### **存储层测试** -``` -✅ 数据保存: 成功 -✅ 数据存在检查: 存在 -✅ 数据加载: 成功 -✅ 键列表: ['test_key'] -✅ 批量保存: 成功 (3/3) -✅ 集合统计: 4 个文件 -✅ 清空集合: 成功 -``` - -### **服务层测试** -``` -✅ 基础服务创建成功: test_service -✅ 服务数据保存: 成功 -✅ 服务数据加载: 成功 -✅ 进度服务创建成功: test_progress_service -✅ 收到进度消息: 3 条 -``` - -### **CLI集成测试** -``` -✅ 媒体管理器使用进度Commander -✅ 场景检测使用进度Commander -✅ 进度命令识别正确 -✅ API风格命令执行成功: 找到 3 个片段 -``` - -## 🎉 总结 - -### **架构成果** -- ✅ **完全满足硬性要求** - 4个核心要求全部实现 -- ✅ **验证通过率100%** - 6/6测试全部通过 -- ✅ **实际可用** - 现有服务完全兼容 -- ✅ **未来就绪** - 具备完整的迁移路径 - -### **技术亮点** -- 🎯 **分层清晰** - CLI/Service/Storage三层架构 -- 🔄 **接口统一** - 标准化的组件接口 -- 📊 **进度标准** - JSON-RPC 2.0进度协议 -- 🔌 **插件化** - 支持存储和服务扩展 - -### **实用价值** -- 💡 **开发效率** - 统一的开发模式 -- 🚀 **部署灵活** - 多种部署方式支持 -- 📈 **扩展性强** - 易于添加新功能 -- 🔧 **维护简单** - 清晰的模块边界 - -这个架构设计完全满足您的硬性要求,提供了从当前JSON存储到未来微服务化的完整演进路径,是一个经过验证的、可持续发展的架构方案! - ---- - -*Python Core 架构 - 命令行优先,进度可视,API就绪,存储灵活!* diff --git a/python_core/cli/commands/template.py b/python_core/cli/commands/template.py index b65340a..9c72a48 100644 --- a/python_core/cli/commands/template.py +++ b/python_core/cli/commands/template.py @@ -6,7 +6,6 @@ from pathlib import Path from typing import Optional, List from dataclasses import asdict import typer -import json from python_core.utils.jsonrpc_enhanced import create_response_handler, create_progress_reporter from python_core.services.template_manager_cloud import TemplateManagerCloud, TemplateInfo from python_core.utils.logger import logger diff --git a/src/App.tsx b/src/App.tsx index e6c9a52..d4b64de 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import HomePage from './pages/HomePage' import EditorPage from './pages/EditorPage' import AIVideoPage from './pages/AIVideoPage' import SettingsPage from './pages/SettingsPage' -import TemplateManagePage from './pages/TemplateManagePage' +import TemplateManagePage from './pages/TemplateManagePageV2' import TemplateDetailPage from './pages/TemplateDetailPage' import ResourceCategoryPage from './pages/ResourceCategoryPage' import ProjectManagePage from './pages/ProjectManagePage' diff --git a/src/hooks/useTemplateProgressV2.ts b/src/hooks/useTemplateProgressV2.ts new file mode 100644 index 0000000..7e0bd8f --- /dev/null +++ b/src/hooks/useTemplateProgressV2.ts @@ -0,0 +1,297 @@ +import { useState, useCallback, useRef, useEffect } from 'react' +import { listen, UnlistenFn } from '@tauri-apps/api/event' +import { TemplateServiceV2, type BatchImportResult } from '../services/TemplateServiceV2' + +// 进度信息接口 +export interface ProgressInfo { + current: number + total: number + message: string + percentage: number +} + +// Hook选项 +export interface UseTemplateProgressOptions { + onSuccess?: (result: BatchImportResult) => void + onError?: (error: Error) => void + onProgress?: (progress: ProgressInfo) => void +} + +// Hook返回值 +export interface UseTemplateProgressReturn { + isExecuting: boolean + progress: ProgressInfo | null + result: BatchImportResult | null + error: Error | null + logs: string[] + batchImport: (sourceFolder: string, userId?: string) => Promise + reset: () => void +} + +export const useTemplateProgressV2 = (options: UseTemplateProgressOptions = {}): UseTemplateProgressReturn => { + const [isExecuting, setIsExecuting] = useState(false) + const [progress, setProgress] = useState(null) + const [result, setResult] = useState(null) + const [error, setError] = useState(null) + const [logs, setLogs] = useState([]) + + const unlistenRef = useRef(null) + const { onSuccess, onError, onProgress } = options + + // 清理监听器 + const cleanup = useCallback(async () => { + if (unlistenRef.current) { + await unlistenRef.current() + unlistenRef.current = null + } + }, []) + + // 重置状态 + const reset = useCallback(() => { + setIsExecuting(false) + setProgress(null) + setResult(null) + setError(null) + setLogs([]) + cleanup() + }, [cleanup]) + + // 添加日志 + const addLog = useCallback((message: string) => { + const timestamp = new Date().toLocaleTimeString() + setLogs(prev => [...prev, `[${timestamp}] ${message}`]) + }, []) + + // 更新进度 + const updateProgress = useCallback((current: number, total: number, message: string) => { + const percentage = total > 0 ? Math.round((current / total) * 100) : 0 + const progressInfo: ProgressInfo = { + current, + total, + message, + percentage + } + + setProgress(progressInfo) + addLog(`进度: ${current}/${total} (${percentage}%) - ${message}`) + + if (onProgress) { + onProgress(progressInfo) + } + }, [addLog, onProgress]) + + // 批量导入模板 + const batchImport = useCallback(async (sourceFolder: string, userId?: string) => { + try { + setIsExecuting(true) + setError(null) + setResult(null) + setProgress(null) + setLogs([]) + + addLog(`开始批量导入模板: ${sourceFolder}`) + + // 监听进度事件 + unlistenRef.current = await listen('template-import-progress', (event: any) => { + const { current, total, message } = event.payload + updateProgress(current, total, message) + }) + + // 执行导入 + const importResult = await TemplateServiceV2.batchImportTemplates(sourceFolder, userId) + + setResult(importResult) + addLog(`导入完成: 成功 ${importResult.imported_count} 个,失败 ${importResult.failed_count} 个`) + + // 显示详细结果 + if (importResult.imported_templates.length > 0) { + addLog('成功导入的模板:') + importResult.imported_templates.forEach(template => { + addLog(` ✓ ${template.name} (${template.id})`) + }) + } + + if (importResult.failed_templates.length > 0) { + addLog('导入失败的模板:') + importResult.failed_templates.forEach(failed => { + addLog(` ✗ ${failed.name}: ${failed.error}`) + }) + } + + if (onSuccess) { + onSuccess(importResult) + } + + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)) + setError(error) + addLog(`导入失败: ${error.message}`) + + if (onError) { + onError(error) + } + } finally { + setIsExecuting(false) + cleanup() + } + }, [addLog, updateProgress, onSuccess, onError, cleanup]) + + // 组件卸载时清理 + useEffect(() => { + return () => { + cleanup() + } + }, [cleanup]) + + return { + isExecuting, + progress, + result, + error, + logs, + batchImport, + reset + } +} + +// 简化版本的进度Hook,用于其他操作 +export const useTemplateOperationV2 = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const execute = useCallback(async (operation: () => Promise): Promise => { + try { + setLoading(true) + setError(null) + const result = await operation() + return result + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)) + setError(error) + return null + } finally { + setLoading(false) + } + }, []) + + const reset = useCallback(() => { + setLoading(false) + setError(null) + }, []) + + return { + loading, + error, + execute, + reset + } +} + +// 模板搜索Hook +export const useTemplateSearchV2 = () => { + const [searchResults, setSearchResults] = useState([]) + const [searching, setSearching] = useState(false) + const [searchError, setSearchError] = useState(null) + + const search = useCallback(async ( + query: string, + includeCloud: boolean = true, + limit: number = 50, + userId?: string + ) => { + try { + setSearching(true) + setSearchError(null) + + if (!query.trim()) { + setSearchResults([]) + return + } + + const results = await TemplateServiceV2.searchTemplates(query, includeCloud, limit, userId) + setSearchResults(results) + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)) + setSearchError(error) + setSearchResults([]) + } finally { + setSearching(false) + } + }, []) + + const searchByTag = useCallback(async ( + tag: string, + includeCloud: boolean = true, + limit: number = 50, + userId?: string + ) => { + try { + setSearching(true) + setSearchError(null) + + const results = await TemplateServiceV2.getTemplatesByTag(tag, includeCloud, limit, userId) + setSearchResults(results) + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)) + setSearchError(error) + setSearchResults([]) + } finally { + setSearching(false) + } + }, []) + + const clearSearch = useCallback(() => { + setSearchResults([]) + setSearchError(null) + }, []) + + return { + searchResults, + searching, + searchError, + search, + searchByTag, + clearSearch + } +} + +// 模板统计Hook +export const useTemplateStatsV2 = () => { + const [stats, setStats] = useState(null) + const [popularTags, setPopularTags] = useState([]) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const loadStats = useCallback(async (userId?: string) => { + try { + setLoading(true) + setError(null) + + const [statsResult, tagsResult] = await Promise.all([ + TemplateServiceV2.getTemplateStats(userId), + TemplateServiceV2.getPopularTags(20, userId) + ]) + + setStats(statsResult) + setPopularTags(tagsResult) + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)) + setError(error) + } finally { + setLoading(false) + } + }, []) + + const refresh = useCallback((userId?: string) => { + loadStats(userId) + }, [loadStats]) + + return { + stats, + popularTags, + loading, + error, + loadStats, + refresh + } +} diff --git a/src/pages/TemplateManagePageV2.tsx b/src/pages/TemplateManagePageV2.tsx new file mode 100644 index 0000000..d6a29a2 --- /dev/null +++ b/src/pages/TemplateManagePageV2.tsx @@ -0,0 +1,564 @@ +import React, { useState, useEffect } from 'react' +import { invoke } from '@tauri-apps/api/core' +import { useNavigate } from 'react-router-dom' +import { TemplateServiceV2, type TemplateInfo, type TemplateStats, type TagInfo } from '../services/TemplateServiceV2' +import { + Search, + Grid, + List, + Upload, + Filter, + Tag, + BarChart3, + RefreshCw, + Download, + Eye, + Trash2 +} from 'lucide-react' + +const TemplateManagePageV2: React.FC = () => { + const navigate = useNavigate() + + // State + const [templates, setTemplates] = useState([]) + const [filteredTemplates, setFilteredTemplates] = useState([]) + const [loading, setLoading] = useState(false) + const [importing, setImporting] = useState(false) + const [searchTerm, setSearchTerm] = useState('') + const [selectedTag, setSelectedTag] = useState('') + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid') + const [includeCloud, setIncludeCloud] = useState(true) + const [showStats, setShowStats] = useState(false) + + // Stats and tags + const [stats, setStats] = useState(null) + const [popularTags, setPopularTags] = useState([]) + + useEffect(() => { + loadTemplates() + loadStats() + loadPopularTags() + }, [includeCloud]) + + useEffect(() => { + filterTemplates() + }, [templates, searchTerm, selectedTag]) + + // 过滤模板 + const filterTemplates = () => { + let filtered = templates + + // 搜索过滤 + if (searchTerm) { + filtered = filtered.filter(template => + template.name.toLowerCase().includes(searchTerm.toLowerCase()) || + template.description.toLowerCase().includes(searchTerm.toLowerCase()) || + template.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase())) + ) + } + + // 标签过滤 + if (selectedTag) { + filtered = filtered.filter(template => + template.tags.includes(selectedTag) + ) + } + + setFilteredTemplates(filtered) + } + + // 加载模板列表 + const loadTemplates = async () => { + try { + setLoading(true) + const templateList = await TemplateServiceV2.getTemplates(includeCloud, 200) + setTemplates(templateList) + } catch (error) { + console.error('Error loading templates:', error) + setTemplates([]) + } finally { + setLoading(false) + } + } + + // 加载统计信息 + const loadStats = async () => { + try { + const templateStats = await TemplateServiceV2.getTemplateStats() + setStats(templateStats) + } catch (error) { + console.error('Error loading stats:', error) + } + } + + // 加载热门标签 + const loadPopularTags = async () => { + try { + const tags = await TemplateServiceV2.getPopularTags(15) + setPopularTags(tags) + } catch (error) { + console.error('Error loading popular tags:', error) + } + } + + // 批量导入 + const handleBatchImport = async () => { + try { + const folderResult = await invoke('select_folder') + if (!folderResult) return + + setImporting(true) + const result = await TemplateServiceV2.batchImportTemplates(folderResult) + + if (result.imported_count > 0) { + await loadTemplates() // 重新加载模板列表 + await loadStats() // 重新加载统计信息 + alert(`导入成功!成功导入 ${result.imported_count} 个模板,失败 ${result.failed_count} 个`) + } else { + alert('导入失败:' + result.message) + } + } catch (error) { + console.error('Import failed:', error) + alert('导入失败: ' + (error instanceof Error ? error.message : 'Unknown error')) + } finally { + setImporting(false) + } + } + + // 删除模板 + const handleDeleteTemplate = async (templateId: string) => { + if (!confirm('确定要删除这个模板吗?此操作不可撤销。')) return + + try { + const success = await TemplateServiceV2.deleteTemplate(templateId) + if (success) { + setTemplates(templates.filter(t => t.id !== templateId)) + await loadStats() // 重新加载统计信息 + } else { + alert('删除失败') + } + } catch (error) { + console.error('Delete failed:', error) + alert('删除失败: ' + (error instanceof Error ? error.message : 'Unknown error')) + } + } + + // 查看模板详情 + const viewTemplateDetail = (template: TemplateInfo) => { + navigate(`/templates/${template.id}`) + } + + // 按标签搜索 + const handleTagClick = async (tag: string) => { + if (selectedTag === tag) { + setSelectedTag('') + return + } + + setSelectedTag(tag) + try { + const templatesByTag = await TemplateServiceV2.getTemplatesByTag(tag, includeCloud, 100) + setTemplates(templatesByTag) + } catch (error) { + console.error('Error loading templates by tag:', error) + } + } + + // 搜索模板 + const handleSearch = async () => { + if (!searchTerm.trim()) { + await loadTemplates() + return + } + + try { + setLoading(true) + const searchResults = await TemplateServiceV2.searchTemplates(searchTerm, includeCloud, 100) + setTemplates(searchResults) + } catch (error) { + console.error('Search failed:', error) + } finally { + setLoading(false) + } + } + + return ( +
+
+ {/* Header */} +
+
+
+

模板管理 V2

+

管理和导入视频编辑模板 - 新版本

+
+
+ + +
+
+
+ + {/* Stats Panel */} + {showStats && stats && ( +
+

模板统计

+
+
+
{stats.total_templates}
+
总模板数
+
+
+
{stats.user_templates}
+
用户模板
+
+
+
{stats.cloud_templates}
+
云端模板
+
+
+
{stats.total_materials}
+
总素材数
+
+
+
+ )} + + {/* Search and Filters */} +
+
+ {/* Search */} +
+
+ + setSearchTerm(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleSearch()} + className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+ +
+ + {/* Controls */} +
+ {/* Cloud Toggle */} + + + {/* View Mode */} +
+ + +
+ + {/* Import Button */} + +
+
+
+ + {/* Popular Tags */} + {popularTags.length > 0 && ( +
+

热门标签

+
+ {popularTags.map((tagInfo) => ( + + ))} + {selectedTag && ( + + )} +
+
+ )} + + {/* Template Grid/List */} +
+ {loading ? ( +
+ + 加载中... +
+ ) : filteredTemplates.length === 0 ? ( +
+
+ +
+

暂无模板

+

点击"批量导入"按钮开始导入模板

+
+ ) : ( + <> +
+

+ 模板列表 ({filteredTemplates.length}) +

+ {selectedTag && ( +
+ 筛选标签: {selectedTag} +
+ )} +
+ + {viewMode === 'grid' ? ( +
+ {filteredTemplates.map((template) => ( + + ))} +
+ ) : ( +
+ {filteredTemplates.map((template) => ( + + ))} +
+ )} + + )} +
+
+
+ ) +} + +// 模板卡片组件 +const TemplateCardV2: React.FC<{ + template: TemplateInfo + onView: (template: TemplateInfo) => void + onDelete: (templateId: string) => void +}> = ({ template, onView, onDelete }) => { + const thumbnailUrl = TemplateServiceV2.getThumbnailUrl(template) + const isCloud = TemplateServiceV2.isCloudTemplate(template) + + return ( +
+ {/* Thumbnail */} +
+ {thumbnailUrl ? ( + {template.name} + ) : ( +
+ +
+ )} + {isCloud && ( +
+ 云端 +
+ )} +
+ + {/* Content */} +
+

{template.name}

+

{template.description}

+ + {/* Stats */} +
+ {TemplateServiceV2.formatDuration(template.duration)} + {template.material_count} 素材 + {template.track_count} 轨道 +
+ + {/* Tags */} + {template.tags.length > 0 && ( +
+ {template.tags.slice(0, 3).map((tag) => ( + + {tag} + + ))} + {template.tags.length > 3 && ( + + +{template.tags.length - 3} + + )} +
+ )} + + {/* Actions */} +
+ + {TemplateServiceV2.formatDate(template.created_at)} + +
+ + +
+
+
+
+ ) +} + +// 模板列表项组件 +const TemplateListItemV2: React.FC<{ + template: TemplateInfo + onView: (template: TemplateInfo) => void + onDelete: (templateId: string) => void +}> = ({ template, onView, onDelete }) => { + const thumbnailUrl = TemplateServiceV2.getThumbnailUrl(template) + const isCloud = TemplateServiceV2.isCloudTemplate(template) + + return ( +
+
+
+ {/* Thumbnail */} +
+ {thumbnailUrl ? ( + {template.name} + ) : ( + + )} + {isCloud && ( +
+ 云 +
+ )} +
+ + {/* Info */} +
+

{template.name}

+

{template.description}

+ {template.tags.length > 0 && ( +
+ {template.tags.slice(0, 5).map((tag) => ( + + {tag} + + ))} +
+ )} +
+
+ +
+ {TemplateServiceV2.formatDuration(template.duration)} + {template.material_count} 素材 + {template.track_count} 轨道 + {TemplateServiceV2.formatDate(template.created_at)} +
+ +
+ + +
+
+
+ ) +} + +export default TemplateManagePageV2 diff --git a/src/services/TemplateServiceV2.ts b/src/services/TemplateServiceV2.ts new file mode 100644 index 0000000..26991f6 --- /dev/null +++ b/src/services/TemplateServiceV2.ts @@ -0,0 +1,379 @@ +import { invoke } from '@tauri-apps/api/core' + +// 统一的响应格式 +export interface TemplateResponse { + success: boolean + message: string + data?: any + output?: string + error?: string +} + +// 模板信息接口 +export interface TemplateInfo { + id: string + name: string + description: string + thumbnail_path: string + draft_content_path: string + resources_path: string + created_at: string + updated_at: string + canvas_config: any + duration: number + material_count: number + track_count: number + tags: string[] + is_cloud: boolean + user_id: string +} + +// 请求接口 +export interface BatchImportRequest { + source_folder: string + user_id?: string + verbose?: boolean + json_output?: boolean +} + +export interface TemplateListRequest { + user_id?: string + include_cloud?: boolean + limit?: number + verbose?: boolean + json_output?: boolean +} + +export interface TemplateGetRequest { + template_id: string + user_id?: string + verbose?: boolean + json_output?: boolean +} + +export interface TemplateDeleteRequest { + template_id: string + user_id?: string + verbose?: boolean + json_output?: boolean +} + +export interface TemplateSearchRequest { + query: string + user_id?: string + include_cloud?: boolean + limit?: number + verbose?: boolean + json_output?: boolean +} + +// 批量导入结果 +export interface BatchImportResult { + imported_count: number + failed_count: number + imported_templates: Array<{ + id: string + name: string + path: string + }> + failed_templates: Array<{ + name: string + path: string + error: string + }> + message: string +} + +// 统计信息 +export interface TemplateStats { + total_templates: number + user_templates: number + cloud_templates: number + total_materials: number + total_tracks: number + total_duration: number + average_duration: number +} + +// 标签信息 +export interface TagInfo { + tag: string + count: number +} + +export class TemplateServiceV2 { + private static currentUserId: string = 'default' + + /** + * 设置当前用户ID + */ + static setUserId(userId: string) { + this.currentUserId = userId + } + + /** + * 批量导入模板 (新版本) + */ + static async batchImportTemplates(sourceFolder: string, userId?: string): Promise { + try { + const request: BatchImportRequest = { + source_folder: sourceFolder, + user_id: userId || this.currentUserId, + verbose: false, + json_output: true + } + + const response = await invoke('batch_import_templates_cli', { request }) + + if (!response.success) { + throw new Error(response.error || response.message || 'Import failed') + } + + return response.data as BatchImportResult + } catch (error) { + console.error('Batch import templates failed:', error) + throw error + } + } + + /** + * 获取模板列表 (新版本) + */ + static async getTemplates(includeCloud: boolean = true, limit: number = 100, userId?: string): Promise { + try { + const request: TemplateListRequest = { + user_id: userId || this.currentUserId, + include_cloud: includeCloud, + limit: limit, + verbose: false, + json_output: true + } + + const response = await invoke('get_templates_cli', { request }) + + if (!response.success) { + throw new Error(response.error || response.message || 'Failed to get templates') + } + + return response.data?.templates || [] + } catch (error) { + console.error('Get templates failed:', error) + throw error + } + } + + /** + * 获取单个模板 (新版本) + */ + static async getTemplate(templateId: string, userId?: string): Promise { + try { + const request: TemplateGetRequest = { + template_id: templateId, + user_id: userId || this.currentUserId, + verbose: false, + json_output: true + } + + const response = await invoke('get_template_cli', { request }) + + if (!response.success) { + throw new Error(response.error || response.message || 'Failed to get template') + } + + return response.data as TemplateInfo + } catch (error) { + console.error('Get template failed:', error) + throw error + } + } + + /** + * 删除模板 (新版本) + */ + static async deleteTemplate(templateId: string, userId?: string): Promise { + try { + const request: TemplateDeleteRequest = { + template_id: templateId, + user_id: userId || this.currentUserId, + verbose: false, + json_output: true + } + + const response = await invoke('delete_template_cli', { request }) + + if (!response.success) { + throw new Error(response.error || response.message || 'Failed to delete template') + } + + return response.data?.deleted || false + } catch (error) { + console.error('Delete template failed:', error) + throw error + } + } + + /** + * 搜索模板 (新功能) + */ + static async searchTemplates( + query: string, + includeCloud: boolean = true, + limit: number = 50, + userId?: string + ): Promise { + try { + const request: TemplateSearchRequest = { + query: query, + user_id: userId || this.currentUserId, + include_cloud: includeCloud, + limit: limit, + verbose: false, + json_output: true + } + + const response = await invoke('search_templates_cli', { request }) + + if (!response.success) { + throw new Error(response.error || response.message || 'Failed to search templates') + } + + return response.data?.templates || [] + } catch (error) { + console.error('Search templates failed:', error) + throw error + } + } + + /** + * 获取模板统计信息 (新功能) + */ + static async getTemplateStats(userId?: string): Promise { + try { + const response = await invoke('get_template_stats_cli', { + user_id: userId || this.currentUserId, + verbose: false + }) + + if (!response.success) { + throw new Error(response.error || response.message || 'Failed to get template stats') + } + + return response.data as TemplateStats + } catch (error) { + console.error('Get template stats failed:', error) + throw error + } + } + + /** + * 获取热门标签 (新功能) + */ + static async getPopularTags(limit: number = 20, userId?: string): Promise { + try { + const response = await invoke('get_popular_tags_cli', { + user_id: userId || this.currentUserId, + limit: limit, + verbose: false + }) + + if (!response.success) { + throw new Error(response.error || response.message || 'Failed to get popular tags') + } + + return response.data?.tags || [] + } catch (error) { + console.error('Get popular tags failed:', error) + throw error + } + } + + /** + * 根据标签获取模板 (新功能) + */ + static async getTemplatesByTag( + tag: string, + includeCloud: boolean = true, + limit: number = 50, + userId?: string + ): Promise { + try { + const response = await invoke('get_templates_by_tag_cli', { + tag: tag, + user_id: userId || this.currentUserId, + include_cloud: includeCloud, + limit: limit, + verbose: false + }) + + if (!response.success) { + throw new Error(response.error || response.message || 'Failed to get templates by tag') + } + + return response.data?.templates || [] + } catch (error) { + console.error('Get templates by tag failed:', error) + throw error + } + } + + /** + * 验证模板文件夹结构 + */ + static validateTemplateFolder(folderPath: string): boolean { + // 这个方法保持不变,依赖后端验证 + return true + } + + /** + * 格式化持续时间 + */ + static formatDuration(seconds: number): string { + const minutes = Math.floor(seconds / 60) + const remainingSeconds = seconds % 60 + return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}` + } + + /** + * 格式化日期 + */ + static formatDate(dateString: string): string { + try { + const date = new Date(dateString) + return date.toLocaleDateString('zh-CN', { + year: 'numeric', + month: 'short', + day: 'numeric' + }) + } catch { + return dateString + } + } + + /** + * 获取模板缩略图URL + */ + static getThumbnailUrl(template: TemplateInfo): string { + if (template.thumbnail_path) { + // 如果是相对路径,转换为绝对路径 + if (!template.thumbnail_path.startsWith('http') && !template.thumbnail_path.startsWith('/')) { + return `file://${template.resources_path}/${template.thumbnail_path}` + } + return template.thumbnail_path + } + return '' + } + + /** + * 检查模板是否为云端模板 + */ + static isCloudTemplate(template: TemplateInfo): boolean { + return template.is_cloud || false + } + + /** + * 检查模板是否属于当前用户 + */ + static isUserTemplate(template: TemplateInfo, userId?: string): boolean { + return template.user_id === (userId || this.currentUserId) + } +} diff --git a/src/services/templateService.ts b/src/services/templateService.ts deleted file mode 100644 index a17ea82..0000000 --- a/src/services/templateService.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { invoke } from '@tauri-apps/api/core' - -export interface TemplateInfo { - id: string - name: string - description: string - thumbnail_path: string - draft_content_path: string - resources_path: string - created_at: string - updated_at: string - canvas_config: any - duration: number - material_count: number - track_count: number - tags: string[] -} - -export interface BatchImportRequest { - source_folder: string -} - -export interface BatchImportResult { - status: boolean - msg: string - imported_count: number - failed_count: number - imported_templates: TemplateInfo[] - failed_templates: Array<{ - name: string - error: string - }> -} - -export interface TemplateResult { - status: boolean - msg?: string - template?: TemplateInfo - templates?: TemplateInfo[] -} - -export class TemplateService { - /** - * Batch import templates from a folder - */ - static async batchImportTemplates(sourceFolder: string): Promise { - try { - const request: BatchImportRequest = { - source_folder: sourceFolder - } - - const response = await invoke('batch_import_templates', { request }) - const result = JSON.parse(response) - - if (!result.status) { - throw new Error(result.error || result.msg || 'Import failed') - } - - return result - } catch (error) { - console.error('Batch import templates failed:', error) - throw error - } - } - - /** - * Get all templates - */ - static async getTemplates(): Promise { - try { - const response = await invoke('get_templates') - const result = JSON.parse(response) - - if (!result.status) { - throw new Error(result.error || result.msg || 'Failed to get templates') - } - - return result - } catch (error) { - console.error('Get templates failed:', error) - throw error - } - } - - /** - * Get a specific template - */ - static async getTemplate(templateId: string): Promise { - try { - const response = await invoke('get_template', { templateId }) - const result = JSON.parse(response) - - if (!result.status) { - throw new Error(result.error || result.msg || 'Failed to get template') - } - - return result - } catch (error) { - console.error('Get template failed:', error) - throw error - } - } - - /** - * Delete a template - */ - static async deleteTemplate(templateId: string): Promise { - try { - const response = await invoke('delete_template', { templateId }) - const result = JSON.parse(response) - - if (!result.status) { - throw new Error(result.error || result.msg || 'Failed to delete template') - } - - return result - } catch (error) { - console.error('Delete template failed:', error) - throw error - } - } - - /** - * Validate template folder structure - */ - static validateTemplateFolder(folderPath: string): boolean { - // This would be implemented to check if the folder contains valid template structure - // For now, we'll rely on the backend validation - return true - } - - /** - * Generate template thumbnail - */ - static async generateThumbnail(templateId: string): Promise { - // This would be implemented to generate a thumbnail from the template - // For now, return empty string - return '' - } - - /** - * Export template - */ - static async exportTemplate(templateId: string, targetPath: string): Promise { - try { - // This would be implemented to export a template to a specific location - // For now, just return true - return true - } catch (error) { - console.error('Export template failed:', error) - return false - } - } - - /** - * Duplicate template - */ - static async duplicateTemplate(templateId: string, newName: string): Promise { - try { - // This would be implemented to create a copy of an existing template - // For now, just return a placeholder result - return { - status: false, - msg: 'Duplicate functionality not implemented yet' - } - } catch (error) { - console.error('Duplicate template failed:', error) - throw error - } - } - - /** - * Update template metadata - */ - static async updateTemplate(templateId: string, updates: Partial): Promise { - try { - // This would be implemented to update template metadata - // For now, just return a placeholder result - return { - status: false, - msg: 'Update functionality not implemented yet' - } - } catch (error) { - console.error('Update template failed:', error) - throw error - } - } - - /** - * Search templates - */ - static async searchTemplates(query: string, filters?: { - tags?: string[] - duration_min?: number - duration_max?: number - material_count_min?: number - material_count_max?: number - }): Promise { - try { - // This would be implemented to search templates with filters - // For now, just get all templates and filter on frontend - const result = await this.getTemplates() - - if (result.status && result.templates) { - const filteredTemplates = result.templates.filter(template => - template.name.toLowerCase().includes(query.toLowerCase()) || - template.description.toLowerCase().includes(query.toLowerCase()) - ) - - return { - status: true, - templates: filteredTemplates - } - } - - return result - } catch (error) { - console.error('Search templates failed:', error) - throw error - } - } - - /** - * Get template statistics - */ - static async getTemplateStats(): Promise<{ - total_templates: number - total_materials: number - total_size: number - most_used_tags: string[] - }> { - try { - const result = await this.getTemplates() - - if (result.status && result.templates) { - const templates = result.templates - const totalTemplates = templates.length - const totalMaterials = templates.reduce((sum, t) => sum + t.material_count, 0) - - // Extract all tags and count frequency - const tagCounts: { [key: string]: number } = {} - templates.forEach(template => { - template.tags.forEach(tag => { - tagCounts[tag] = (tagCounts[tag] || 0) + 1 - }) - }) - - const mostUsedTags = Object.entries(tagCounts) - .sort(([, a], [, b]) => b - a) - .slice(0, 10) - .map(([tag]) => tag) - - return { - total_templates: totalTemplates, - total_materials: totalMaterials, - total_size: 0, // Would need to calculate from file sizes - most_used_tags: mostUsedTags - } - } - - return { - total_templates: 0, - total_materials: 0, - total_size: 0, - most_used_tags: [] - } - } catch (error) { - console.error('Get template stats failed:', error) - return { - total_templates: 0, - total_materials: 0, - total_size: 0, - most_used_tags: [] - } - } - } -}