Add File
This commit is contained in:
283
src/landppt/services/project_manager.py
Normal file
283
src/landppt/services/project_manager.py
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
"""
|
||||||
|
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")
|
||||||
Reference in New Issue
Block a user