Files
LandPPT/src/landppt/services/project_manager.py
2025-11-07 09:05:19 +08:00

284 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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")