""" Project and TODO Board Management Service Implements the comprehensive project lifecycle management as specified in requires.md """ import uuid import time import json import logging from typing import Dict, List, Optional, Any from datetime import datetime from ..api.models import ( PPTProject, TodoBoard, TodoStage, ProjectListResponse, PPTGenerationRequest, PPTOutline, EnhancedPPTOutline ) # Configure logger for this module logger = logging.getLogger(__name__) class ProjectManager: """Manages PPT projects with TODO board workflow""" def __init__(self): self.projects: Dict[str, PPTProject] = {} self.todo_boards: Dict[str, TodoBoard] = {} async def create_project(self, request: PPTGenerationRequest) -> PPTProject: """Create a new PPT project with TODO board""" project_id = str(uuid.uuid4()) # Create TODO board with 5 stages as per requirements todo_board = self._create_todo_board(project_id, request) project = PPTProject( project_id=project_id, title=f"{request.topic} - {request.scenario}", scenario=request.scenario, topic=request.topic, requirements=request.requirements, status="draft", todo_board=todo_board ) self.projects[project_id] = project self.todo_boards[project_id] = todo_board return project def _create_todo_board(self, project_id: str, request: PPTGenerationRequest) -> TodoBoard: """Create initial TODO board with placeholder stages (will be updated after requirements confirmation)""" stages = [ TodoStage( id="requirements_confirmation", name="需求确认", description="AI根据用户设定的场景和上传的文件内容提供补充信息用来确认用户的任务需求", subtasks=[ "生成需求确认表单,包含Topic/Type/Content focus/Tech highlights/Target audience等字段" ] ) ] return TodoBoard( task_id=project_id, title={request.topic}, stages=stages ) def update_todo_board_with_confirmed_requirements(self, project_id: str, confirmed_requirements: Dict[str, Any]) -> bool: """Update TODO board with full workflow after requirements confirmation""" try: todo_board = self.todo_boards.get(project_id) if not todo_board: return False # Create simplified workflow stages - 只有3个阶段 stages = [ TodoStage( id="requirements_confirmation", name="需求确认", description="确认PPT主题、内容重点、技术亮点和目标受众", status="completed", # This stage is completed when requirements are confirmed progress=100.0, subtasks=["需求确认完成"] # Simplified - only title ), TodoStage( id="outline_generation", name="大纲生成", description="基于确认的需求生成PPT大纲结构", status="pending", # Start as pending progress=0.0, subtasks=["生成PPT大纲"] # Simplified - only title ), TodoStage( id="ppt_creation", name="PPT生成", description="根据大纲生成完整的PPT页面", status="pending", # Start as pending progress=0.0, subtasks=["PPT生成"] # Simplified - only title ) ] todo_board.stages = stages todo_board.updated_at = time.time() # Recalculate overall progress correctly completed_stages = sum(1 for s in stages if s.status == "completed") todo_board.overall_progress = (completed_stages / len(stages)) * 100 # Set current stage index to the first non-completed stage todo_board.current_stage_index = 0 for i, stage in enumerate(stages): if stage.status != "completed": todo_board.current_stage_index = i break return True except Exception as e: print(f"Error updating TODO board: {e}") return False async def get_project(self, project_id: str) -> Optional[PPTProject]: """Get project by ID from memory only""" return self.projects.get(project_id) async def list_projects(self, page: int = 1, page_size: int = 10, status: Optional[str] = None) -> ProjectListResponse: """List projects with pagination""" projects = list(self.projects.values()) # Filter by status if provided if status: projects = [p for p in projects if p.status == status] # Sort by updated_at descending projects.sort(key=lambda x: x.updated_at, reverse=True) # Pagination start_idx = (page - 1) * page_size end_idx = start_idx + page_size paginated_projects = projects[start_idx:end_idx] return ProjectListResponse( projects=paginated_projects, total=len(projects), page=page, page_size=page_size ) async def update_project_status(self, project_id: str, status: str) -> bool: """Update project status""" if project_id in self.projects: self.projects[project_id].status = status self.projects[project_id].updated_at = time.time() return True return False async def get_todo_board(self, project_id: str) -> Optional[TodoBoard]: """Get TODO board for project""" return self.todo_boards.get(project_id) async def update_stage_status(self, project_id: str, stage_id: str, status: str, progress: float = None, result: Dict[str, Any] = None) -> bool: """Update stage status in TODO board""" todo_board = self.todo_boards.get(project_id) if not todo_board: return False # Find and update stage for i, stage in enumerate(todo_board.stages): if stage.id == stage_id: stage.status = status stage.updated_at = time.time() if progress is not None: stage.progress = progress if result is not None: stage.result = result # Update current stage index if this stage is completed if status == "completed" and i == todo_board.current_stage_index: todo_board.current_stage_index = min(i + 1, len(todo_board.stages) - 1) # Update overall progress completed_stages = sum(1 for s in todo_board.stages if s.status == "completed") todo_board.overall_progress = (completed_stages / len(todo_board.stages)) * 100 todo_board.updated_at = time.time() # Also update the project's TODO board reference if project_id in self.projects: self.projects[project_id].todo_board = todo_board self.projects[project_id].updated_at = time.time() logger.info(f"Updated stage {stage_id} to {status}, overall progress: {todo_board.overall_progress}%") return True return False async def add_subtask(self, project_id: str, stage_id: str, subtask: str) -> bool: """Add subtask to a stage""" todo_board = self.todo_boards.get(project_id) if not todo_board: return False for stage in todo_board.stages: if stage.id == stage_id: stage.subtasks.append(subtask) stage.updated_at = time.time() return True return False async def save_project_version(self, project_id: str, version_data: Dict[str, Any]) -> bool: """Save a version of the project""" project = self.projects.get(project_id) if not project: return False version_info = { "version": project.version, "timestamp": time.time(), "data": version_data, "description": f"Version {project.version} - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" } project.versions.append(version_info) project.version += 1 project.updated_at = time.time() return True async def get_project_versions(self, project_id: str) -> List[Dict[str, Any]]: """Get all versions of a project""" project = self.projects.get(project_id) if not project: return [] return project.versions async def restore_project_version(self, project_id: str, version: int) -> bool: """Restore project to a specific version""" project = self.projects.get(project_id) if not project: return False # Find the version target_version = None for v in project.versions: if v["version"] == version: target_version = v break if not target_version: return False # Restore the data version_data = target_version["data"] if "outline" in version_data: project.outline = PPTOutline(**version_data["outline"]) if "slides_html" in version_data: project.slides_html = version_data["slides_html"] project.updated_at = time.time() return True async def delete_project(self, project_id: str) -> bool: """Delete a project""" if project_id in self.projects: del self.projects[project_id] if project_id in self.todo_boards: del self.todo_boards[project_id] return True return False async def archive_project(self, project_id: str) -> bool: """Archive a project""" return await self.update_project_status(project_id, "archived")