Add File
This commit is contained in:
189
src/landppt/services/progress_tracker.py
Normal file
189
src/landppt/services/progress_tracker.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
"""
|
||||||
|
Progress Tracker for Speech Script Generation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ProgressInfo:
|
||||||
|
"""Progress information for speech script generation"""
|
||||||
|
task_id: str
|
||||||
|
project_id: str
|
||||||
|
total_slides: int
|
||||||
|
completed_slides: int
|
||||||
|
failed_slides: int
|
||||||
|
skipped_slides: int
|
||||||
|
current_slide: Optional[int] = None
|
||||||
|
current_slide_title: Optional[str] = None
|
||||||
|
status: str = "running" # running, completed, failed
|
||||||
|
message: str = ""
|
||||||
|
start_time: float = 0
|
||||||
|
last_update: float = 0
|
||||||
|
error_details: list = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.start_time == 0:
|
||||||
|
self.start_time = time.time()
|
||||||
|
self.last_update = time.time()
|
||||||
|
if self.error_details is None:
|
||||||
|
self.error_details = []
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
"""Convert to dictionary for JSON serialization"""
|
||||||
|
data = asdict(self)
|
||||||
|
# Add computed properties
|
||||||
|
data['progress_percentage'] = self.progress_percentage
|
||||||
|
data['elapsed_time'] = self.elapsed_time
|
||||||
|
return data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def progress_percentage(self) -> float:
|
||||||
|
"""Calculate progress percentage"""
|
||||||
|
if self.total_slides == 0:
|
||||||
|
return 0
|
||||||
|
return (self.completed_slides / self.total_slides) * 100
|
||||||
|
|
||||||
|
@property
|
||||||
|
def elapsed_time(self) -> float:
|
||||||
|
"""Get elapsed time in seconds"""
|
||||||
|
return time.time() - self.start_time
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressTracker:
|
||||||
|
"""Thread-safe progress tracker for speech script generation"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._progress_data: Dict[str, ProgressInfo] = {}
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
def create_task(self, task_id: str, project_id: str, total_slides: int) -> ProgressInfo:
|
||||||
|
"""Create a new progress tracking task"""
|
||||||
|
with self._lock:
|
||||||
|
progress = ProgressInfo(
|
||||||
|
task_id=task_id,
|
||||||
|
project_id=project_id,
|
||||||
|
total_slides=total_slides,
|
||||||
|
completed_slides=0,
|
||||||
|
failed_slides=0,
|
||||||
|
skipped_slides=0,
|
||||||
|
status="running",
|
||||||
|
message="开始生成演讲稿..."
|
||||||
|
)
|
||||||
|
self._progress_data[task_id] = progress
|
||||||
|
return progress
|
||||||
|
|
||||||
|
def update_progress(self, task_id: str, **kwargs) -> Optional[ProgressInfo]:
|
||||||
|
"""Update progress for a task"""
|
||||||
|
with self._lock:
|
||||||
|
if task_id not in self._progress_data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
progress = self._progress_data[task_id]
|
||||||
|
|
||||||
|
# Update fields
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if hasattr(progress, key):
|
||||||
|
setattr(progress, key, value)
|
||||||
|
|
||||||
|
progress.last_update = time.time()
|
||||||
|
return progress
|
||||||
|
|
||||||
|
def get_progress(self, task_id: str) -> Optional[ProgressInfo]:
|
||||||
|
"""Get progress for a task"""
|
||||||
|
with self._lock:
|
||||||
|
return self._progress_data.get(task_id)
|
||||||
|
|
||||||
|
def complete_task(self, task_id: str, message: str = "生成完成") -> Optional[ProgressInfo]:
|
||||||
|
"""Mark task as completed"""
|
||||||
|
return self.update_progress(
|
||||||
|
task_id,
|
||||||
|
status="completed",
|
||||||
|
message=message
|
||||||
|
)
|
||||||
|
|
||||||
|
def fail_task(self, task_id: str, error_message: str) -> Optional[ProgressInfo]:
|
||||||
|
"""Mark task as failed"""
|
||||||
|
return self.update_progress(
|
||||||
|
task_id,
|
||||||
|
status="failed",
|
||||||
|
message=f"生成失败: {error_message}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def add_slide_completed(self, task_id: str, slide_index: int, slide_title: str) -> Optional[ProgressInfo]:
|
||||||
|
"""Mark a slide as completed"""
|
||||||
|
with self._lock:
|
||||||
|
if task_id not in self._progress_data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
progress = self._progress_data[task_id]
|
||||||
|
progress.completed_slides += 1
|
||||||
|
progress.current_slide = slide_index
|
||||||
|
progress.current_slide_title = slide_title
|
||||||
|
progress.message = f"已完成第{slide_index + 1}页: {slide_title}"
|
||||||
|
progress.last_update = time.time()
|
||||||
|
|
||||||
|
return progress
|
||||||
|
|
||||||
|
def add_slide_failed(self, task_id: str, slide_index: int, slide_title: str, error: str) -> Optional[ProgressInfo]:
|
||||||
|
"""Mark a slide as failed"""
|
||||||
|
with self._lock:
|
||||||
|
if task_id not in self._progress_data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
progress = self._progress_data[task_id]
|
||||||
|
progress.failed_slides += 1
|
||||||
|
progress.current_slide = slide_index
|
||||||
|
progress.current_slide_title = slide_title
|
||||||
|
progress.message = f"第{slide_index + 1}页生成失败: {slide_title}"
|
||||||
|
progress.error_details.append({
|
||||||
|
'slide_index': slide_index,
|
||||||
|
'slide_title': slide_title,
|
||||||
|
'error': error
|
||||||
|
})
|
||||||
|
progress.last_update = time.time()
|
||||||
|
|
||||||
|
return progress
|
||||||
|
|
||||||
|
def add_slide_skipped(self, task_id: str, slide_index: int, slide_title: str, reason: str) -> Optional[ProgressInfo]:
|
||||||
|
"""Mark a slide as skipped"""
|
||||||
|
with self._lock:
|
||||||
|
if task_id not in self._progress_data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
progress = self._progress_data[task_id]
|
||||||
|
progress.skipped_slides += 1
|
||||||
|
progress.current_slide = slide_index
|
||||||
|
progress.current_slide_title = slide_title
|
||||||
|
progress.message = f"第{slide_index + 1}页已跳过: {slide_title}"
|
||||||
|
progress.last_update = time.time()
|
||||||
|
|
||||||
|
return progress
|
||||||
|
|
||||||
|
def cleanup_old_tasks(self, max_age_seconds: int = 3600):
|
||||||
|
"""Clean up old completed/failed tasks"""
|
||||||
|
current_time = time.time()
|
||||||
|
with self._lock:
|
||||||
|
to_remove = []
|
||||||
|
for task_id, progress in self._progress_data.items():
|
||||||
|
if (progress.status in ["completed", "failed"] and
|
||||||
|
current_time - progress.last_update > max_age_seconds):
|
||||||
|
to_remove.append(task_id)
|
||||||
|
|
||||||
|
for task_id in to_remove:
|
||||||
|
del self._progress_data[task_id]
|
||||||
|
|
||||||
|
def remove_task(self, task_id: str) -> bool:
|
||||||
|
"""Remove a specific task"""
|
||||||
|
with self._lock:
|
||||||
|
if task_id in self._progress_data:
|
||||||
|
del self._progress_data[task_id]
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Global progress tracker instance
|
||||||
|
progress_tracker = ProgressTracker()
|
||||||
Reference in New Issue
Block a user