# 👨‍💻 AutoClip Desktop 开发者指南 ## 📋 目录 - [项目架构](#项目架构) - [开发环境设置](#开发环境设置) - [代码结构](#代码结构) - [核心模块](#核心模块) - [API接口](#api接口) - [前端开发](#前端开发) - [后端开发](#后端开发) - [Tauri集成](#tauri集成) - [构建和部署](#构建和部署) - [测试](#测试) - [贡献指南](#贡献指南) ## 🏗️ 项目架构 ### 整体架构 ``` AutoClip Desktop ├── Frontend (React + TypeScript + Ant Design) ├── Backend (Python + FastAPI + Celery) ├── Tauri (Rust + WebView) └── Resources (FFmpeg + 模型文件) ``` ### 技术栈 - **前端**: React 18, TypeScript, Ant Design, Vite - **后端**: Python 3.9+, FastAPI, Celery, SQLAlchemy - **桌面**: Tauri 2.0, Rust - **数据库**: SQLite (桌面模式) - **任务队列**: Celery with SQLite transport - **AI服务**: OpenAI, DashScope, Google Gemini - **语音识别**: Whisper, 云端API ## 🛠️ 开发环境设置 ### 系统要求 - **Node.js**: 18.0+ - **Python**: 3.9+ - **Rust**: 1.70+ - **Git**: 2.0+ ### 安装步骤 1. **克隆项目** ```bash git clone https://github.com/your-org/autoclip-desktop.git cd autoclip-desktop ``` 2. **安装前端依赖** ```bash cd frontend npm install ``` 3. **安装后端依赖** ```bash cd ../backend pip install -r requirements.txt ``` 4. **安装Tauri依赖** ```bash cd ../src-tauri cargo install tauri-cli ``` 5. **设置环境变量** ```bash # 复制环境变量模板 cp .env.example .env # 编辑环境变量 nano .env ``` ### 开发环境配置 ```bash # 启动开发环境 npm run dev # 启动后端服务 python backend/desktop_main.py # 构建Tauri应用 npm run tauri build ``` ## 📁 代码结构 ### 前端结构 ``` frontend/ ├── src/ │ ├── components/ # 通用组件 │ │ ├── ErrorBoundary.tsx │ │ ├── OfflineIndicator.tsx │ │ └── ... │ ├── pages/ # 页面组件 │ │ ├── HomePage.tsx │ │ ├── SettingsPage.tsx │ │ ├── OnboardingPage.tsx │ │ └── ... │ ├── hooks/ # 自定义Hooks │ │ ├── useFirstRun.ts │ │ ├── useDesktopConfig.ts │ │ └── ... │ ├── services/ # API服务 │ │ ├── api.ts │ │ ├── projectApi.ts │ │ └── ... │ ├── store/ # 状态管理 │ │ ├── useProjectStore.ts │ │ ├── useConfigStore.ts │ │ └── ... │ ├── utils/ # 工具函数 │ │ ├── apiUtils.ts │ │ ├── errorHandler.ts │ │ └── ... │ └── types/ # 类型定义 │ ├── api.ts │ ├── project.ts │ └── ... ├── public/ # 静态资源 └── package.json ``` ### 后端结构 ``` backend/ ├── api/ # API路由 │ └── v1/ │ ├── projects.py │ ├── settings.py │ ├── health.py │ └── ... ├── core/ # 核心模块 │ ├── desktop_config.py │ ├── llm_providers.py │ ├── speech_recognition.py │ └── ... ├── services/ # 业务服务 │ ├── project_service.py │ ├── video_service.py │ ├── ai_service.py │ └── ... ├── models/ # 数据模型 │ ├── project.py │ ├── clip.py │ └── ... ├── utils/ # 工具模块 │ ├── error_handler.py │ ├── performance_config.py │ ├── chunked_upload.py │ └── ... ├── tasks/ # Celery任务 │ ├── __init__.py │ ├── video_tasks.py │ └── ... ├── desktop_main.py # 桌面应用入口 ├── desktop_celery.py # 桌面Celery配置 └── requirements.txt ``` ### Tauri结构 ``` src-tauri/ ├── src/ │ ├── lib.rs # 主入口 │ ├── commands.rs # Tauri命令 │ ├── backend_manager.rs # 后端管理 │ └── tray.rs # 系统托盘 ├── Cargo.toml # Rust依赖 ├── tauri.conf.json # Tauri配置 └── resources/ # 资源文件 └── ffmpeg/ # FFmpeg二进制 ``` ## 🔧 核心模块 ### 1. 桌面配置管理 **文件**: `backend/core/desktop_config.py` ```python class DesktopConfig: """桌面应用配置管理""" def __init__(self): self.data_dir = self._get_desktop_data_dir() self.config_file = self.data_dir / "config.json" self.settings_file = self.data_dir / "settings.json" def save_desktop_config(self, config: 'DesktopConfig') -> bool: """保存桌面配置""" try: config_dict = config.dict() # 递归转换Path对象为字符串 config_dict = self._convert_paths_to_strings(config_dict) with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(config_dict, f, ensure_ascii=False, indent=2) return True except Exception as e: logger.error(f"保存桌面配置失败: {e}") return False ``` ### 2. 语音识别服务 **文件**: `backend/core/speech_recognition.py` ```python class SpeechRecognizer: """语音识别服务""" def __init__(self, config: SpeechRecognitionConfig): self.config = config self.recognizer = self._create_recognizer() def transcribe_audio(self, audio_path: str) -> str: """转写音频文件""" if self.config.provider == "whisper_local": return self._transcribe_with_whisper(audio_path) elif self.config.provider == "openai": return self._transcribe_with_openai(audio_path) # ... 其他服务 ``` ### 3. 性能优化 **文件**: `backend/utils/performance_config.py` ```python class PerformanceConfig: """性能配置管理""" def __init__(self, level: str = "medium"): self.level = level self.settings = self._get_settings_for_level(level) def _get_settings_for_level(self, level: str) -> PerformanceSettings: """根据级别获取性能设置""" levels = { "low": PerformanceSettings( max_concurrent_tasks=1, chunk_size_mb=5, upload_timeout_seconds=300 ), "medium": PerformanceSettings( max_concurrent_tasks=2, chunk_size_mb=10, upload_timeout_seconds=600 ), "high": PerformanceSettings( max_concurrent_tasks=4, chunk_size_mb=20, upload_timeout_seconds=1200 ) } return levels.get(level, levels["medium"]) ``` ## 🔌 API接口 ### 项目管理API ```python # 创建项目 @router.post("/projects", response_model=ProjectResponse) async def create_project(project: ProjectCreate): """创建新项目""" return await project_service.create_project(project) # 获取项目列表 @router.get("/projects", response_model=List[ProjectResponse]) async def get_projects(): """获取项目列表""" return await project_service.get_projects() # 获取项目详情 @router.get("/projects/{project_id}", response_model=ProjectDetailResponse) async def get_project(project_id: int): """获取项目详情""" return await project_service.get_project(project_id) ``` ### 设置管理API ```python # 更新设置 @router.put("/settings", response_model=SettingsResponse) async def update_settings(settings: DesktopSettings): """更新应用设置""" config = get_desktop_config() config.settings = settings save_desktop_config(config) return SettingsResponse(success=True) # 测试API连接 @router.post("/settings/test-api", response_model=ApiTestResponse) async def test_api_connection(request: TestApiRequest): """测试API连接""" provider = create_llm_provider(request.provider, request.api_key) success = provider.test_connection() return ApiTestResponse(success=success) ``` ## 🎨 前端开发 ### 状态管理 **文件**: `frontend/src/store/useProjectStore.ts` ```typescript interface ProjectStore { projects: Project[] currentProject: Project | null loading: boolean error: string | null // Actions fetchProjects: () => Promise createProject: (project: ProjectCreate) => Promise updateProject: (id: number, updates: Partial) => Promise deleteProject: (id: number) => Promise } export const useProjectStore = create((set, get) => ({ projects: [], currentProject: null, loading: false, error: null, fetchProjects: async () => { set({ loading: true, error: null }) try { const projects = await projectApi.getProjects() set({ projects, loading: false }) } catch (error) { set({ error: error.message, loading: false }) } }, // ... 其他actions })) ``` ### 自定义Hooks **文件**: `frontend/src/hooks/useFirstRun.ts` ```typescript export const useFirstRun = () => { const [isFirstRun, setIsFirstRun] = useState(null) const [loading, setLoading] = useState(true) const checkFirstRun = async () => { try { const config = await configApi.getConfig() const hasApiKey = !!(config.settings?.llm?.api_key) setIsFirstRun(!hasApiKey) } catch (error) { console.error('检查首次运行状态失败:', error) setIsFirstRun(true) } finally { setLoading(false) } } useEffect(() => { checkFirstRun() }, []) return { isFirstRun, loading, refresh: checkFirstRun } } ``` ### 错误处理 **文件**: `frontend/src/utils/errorHandler.ts` ```typescript class ErrorHandler { private static instance: ErrorHandler public static getInstance(): ErrorHandler { if (!ErrorHandler.instance) { ErrorHandler.instance = new ErrorHandler() } return ErrorHandler.instance } public handleError(error: any, category: ErrorCategory = 'SYSTEM', context?: string) { let errorMessage = '发生未知错误,请重试' let errorLevel: 'error' | 'warning' | 'info' = 'error' if (error.response) { // 处理HTTP错误 errorMessage = error.response.data?.message || `服务器错误: ${error.response.status}` if (error.response.status === 429) { errorMessage = '系统正在处理其他项目,请稍后再试' errorLevel = 'warning' } } else if (error.request) { // 处理网络错误 errorMessage = '网络连接失败,请检查网络或后端服务是否运行' } else if (error.message) { // 处理其他错误 errorMessage = error.message } const finalMessage = context ? `[${context}] ${errorMessage}` : errorMessage console.error(`[${category} Error] ${finalMessage}`, error) if (errorLevel === 'warning') { message.warning(finalMessage) } else { message.error(finalMessage) } } } ``` ## 🐍 后端开发 ### 服务管理 **文件**: `backend/desktop_main.py` ```python class DesktopServiceManager: """桌面服务管理器""" def __init__(self, config: DesktopConfig): self.config = config self.is_running = False self.server_thread = None self.celery_worker_process = None def start(self): """启动所有服务""" if self.is_running: return try: # 启动FastAPI服务器 self._start_fastapi_server() # 启动Celery Worker self._start_celery_worker() self.is_running = True self.start_time = time.time() logger.info("✅ 所有服务启动成功") except Exception as e: logger.error(f"❌ 服务启动失败: {e}") self.stop() raise def stop(self): """停止所有服务""" if not self.is_running: return try: # 停止Celery Worker进程 if hasattr(self, 'celery_worker_process') and self.celery_worker_process: self.celery_worker_process.terminate() self.celery_worker_process.wait(timeout=5) # 停止FastAPI服务器 if self.server_thread and self.server_thread.is_alive(): self.server_thread.join(timeout=5) self.is_running = False logger.info("✅ 服务已停止") except Exception as e: logger.error(f"❌ 停止服务失败: {e}") ``` ### 任务处理 **文件**: `backend/tasks/video_tasks.py` ```python @celery_app.task(bind=True) def process_video_task(self, project_id: int, video_path: str): """处理视频任务""" try: # 更新任务状态 self.update_state(state='PROGRESS', meta={'status': '开始处理视频'}) # 提取音频 audio_path = extract_audio(video_path) self.update_state(state='PROGRESS', meta={'status': '音频提取完成'}) # 语音识别 transcript = transcribe_audio(audio_path) self.update_state(state='PROGRESS', meta={'status': '语音识别完成'}) # 生成切片 clips = generate_clips(transcript, video_path) self.update_state(state='PROGRESS', meta={'status': '切片生成完成'}) # 保存结果 save_project_results(project_id, clips) return {'status': '处理完成', 'clips_count': len(clips)} except Exception as e: logger.error(f"视频处理失败: {e}") raise self.retry(exc=e, countdown=60, max_retries=3) ``` ### 错误处理 **文件**: `backend/utils/error_handler.py` ```python class AutoClipsException(Exception): """AutoClip自定义异常""" def __init__(self, message: str, category: ErrorCategory = ErrorCategory.SYSTEM, error_code: ErrorCode = ErrorCode.UNKNOWN_ERROR, details: dict = None): self.message = message self.category = category self.error_code = error_code self.details = details or {} super().__init__(self.message) def handle_autoclips_exception(exc: AutoClipsException, request_id: str = None) -> JSONResponse: """处理AutoClip异常""" status_code = 500 if exc.category == ErrorCategory.VALIDATION: status_code = 422 elif exc.category == ErrorCategory.CONFIGURATION: status_code = 400 elif exc.category == ErrorCategory.NETWORK: status_code = 503 return JSONResponse( status_code=status_code, content={ "success": False, "error_code": exc.error_code.value, "message": exc.message, "details": exc.details, "request_id": request_id, "timestamp": time.time() } ) ``` ## 🦀 Tauri集成 ### 命令定义 **文件**: `src-tauri/src/commands.rs` ```rust #[tauri::command] pub async fn start_backend_service( manager: State<'_, BackendManager> ) -> Result { match manager.start().await { Ok(_) => Ok(BackendStatus::Running), Err(e) => Err(format!("启动后端服务失败: {}", e)), } } #[tauri::command] pub async fn stop_backend_service( manager: State<'_, BackendManager> ) -> Result { match manager.stop().await { Ok(_) => Ok(BackendStatus::Stopped), Err(e) => Err(format!("停止后端服务失败: {}", e)), } } #[tauri::command] pub async fn get_service_status( manager: State<'_, BackendManager> ) -> Result { Ok(manager.get_status().await) } ``` ### 后端管理 **文件**: `src-tauri/src/backend_manager.rs` ```rust pub struct BackendManager { status: BackendStatus, process: Option, } impl BackendManager { pub fn new() -> Self { Self { status: BackendStatus::Stopped, process: None, } } pub async fn start(&mut self) -> Result<(), String> { if self.status == BackendStatus::Running { return Ok(()); } // 启动后端进程 let mut cmd = Command::new("python") .arg("backend/desktop_main.py") .current_dir(std::env::current_dir().unwrap()) .spawn() .map_err(|e| format!("启动后端进程失败: {}", e))?; self.process = Some(cmd); self.status = BackendStatus::Running; Ok(()) } pub async fn stop(&mut self) -> Result<(), String> { if let Some(mut process) = self.process.take() { process.kill().map_err(|e| format!("停止后端进程失败: {}", e))?; } self.status = BackendStatus::Stopped; Ok(()) } } ``` ### 系统托盘 **文件**: `src-tauri/src/tray.rs` ```rust pub fn setup_system_tray(app_handle: &AppHandle) -> Result<(), Box> { let tray_menu = MenuBuilder::new() .item(&MenuItem::new("显示主窗口", "show_main_window")) .separator() .item(&MenuItem::new("启动服务", "start_service")) .item(&MenuItem::new("停止服务", "stop_service")) .separator() .item(&MenuItem::new("退出", "quit_app")) .build()?; let _tray = TrayIconBuilder::new() .icon(app_handle.default_window_icon().unwrap().clone()) .menu(&tray_menu) .on_menu_event(move |app, event| { match event.id.as_ref() { "show_main_window" => { if let Some(window) = app.get_window("main") { let _ = window.show(); let _ = window.set_focus(); } } "start_service" => { // 启动服务逻辑 } "stop_service" => { // 停止服务逻辑 } "quit_app" => { app.exit(0); } _ => {} } }) .build(app_handle)?; Ok(()) } ``` ## 🏗️ 构建和部署 ### 前端构建 ```bash # 开发环境 npm run dev # 生产构建 npm run build # 预览构建结果 npm run preview ``` ### 后端构建 ```bash # 安装依赖 pip install -r requirements.txt # 运行测试 pytest # 代码检查 flake8 backend/ black backend/ ``` ### Tauri构建 ```bash # 开发模式 npm run tauri dev # 生产构建 npm run tauri build # 构建特定平台 npm run tauri build -- --target x86_64-apple-darwin # macOS Intel npm run tauri build -- --target aarch64-apple-darwin # macOS ARM npm run tauri build -- --target x86_64-pc-windows-msvc # Windows npm run tauri build -- --target x86_64-unknown-linux-gnu # Linux ``` ### 跨平台构建 ```bash # 构建所有平台 npm run build:all # 构建特定平台 npm run build:macos npm run build:windows npm run build:linux ``` ## 🧪 测试 ### 前端测试 ```bash # 运行测试 npm test # 运行测试并生成覆盖率报告 npm run test:coverage # 运行E2E测试 npm run test:e2e ``` ### 后端测试 ```bash # 运行所有测试 pytest # 运行特定测试 pytest tests/test_project_service.py # 运行测试并生成覆盖率报告 pytest --cov=backend tests/ # 运行性能测试 pytest tests/performance/ ``` ### 集成测试 ```bash # 启动测试环境 docker-compose -f docker-compose.test.yml up -d # 运行集成测试 pytest tests/integration/ # 清理测试环境 docker-compose -f docker-compose.test.yml down ``` ## 🤝 贡献指南 ### 开发流程 1. **Fork项目** ```bash git clone https://github.com/your-username/autoclip-desktop.git cd autoclip-desktop ``` 2. **创建分支** ```bash git checkout -b feature/your-feature-name ``` 3. **开发功能** - 编写代码 - 添加测试 - 更新文档 4. **提交代码** ```bash git add . git commit -m "feat: add your feature" git push origin feature/your-feature-name ``` 5. **创建Pull Request** - 在GitHub上创建PR - 填写详细的描述 - 等待代码审查 ### 代码规范 #### 前端规范 - 使用TypeScript - 遵循ESLint规则 - 使用Prettier格式化 - 组件使用函数式组件 - 使用Hooks进行状态管理 #### 后端规范 - 使用Python 3.9+ - 遵循PEP 8规范 - 使用类型注解 - 编写文档字符串 - 使用Pydantic进行数据验证 #### Rust规范 - 使用Rust 1.70+ - 遵循Clippy建议 - 使用rustfmt格式化 - 编写文档注释 - 使用Result处理错误 ### 提交信息规范 ``` ():