import typing import time from dataclasses import dataclass, field @dataclass class UISnapshot: """Capture of restorable UI state.""" ai_input: str project_system_prompt: str global_system_prompt: str base_system_prompt: str use_default_base_prompt: bool temperature: float top_p: float max_tokens: int auto_add_history: bool disc_entries: list[dict] files: list[dict] screenshots: list[str] def to_dict(self) -> dict: return { "ai_input": self.ai_input, "project_system_prompt": self.project_system_prompt, "global_system_prompt": self.global_system_prompt, "base_system_prompt": self.base_system_prompt, "use_default_base_prompt": self.use_default_base_prompt, "temperature": self.temperature, "top_p": self.top_p, "max_tokens": self.max_tokens, "auto_add_history": self.auto_add_history, "disc_entries": self.disc_entries, "files": self.files, "screenshots": self.screenshots } @classmethod def from_dict(cls, data: dict) -> "UISnapshot": return cls( ai_input=data.get("ai_input", ""), project_system_prompt=data.get("project_system_prompt", ""), global_system_prompt=data.get("global_system_prompt", ""), base_system_prompt=data.get("base_system_prompt", ""), use_default_base_prompt=data.get("use_default_base_prompt", True), temperature=data.get("temperature", 0.0), top_p=data.get("top_p", 1.0), max_tokens=data.get("max_tokens", 4096), auto_add_history=data.get("auto_add_history", False), disc_entries=data.get("disc_entries", []), files=data.get("files", []), screenshots=data.get("screenshots", []) ) @dataclass class HistoryEntry: state: typing.Any description: str timestamp: float = field(default_factory=lambda: time.time()) class HistoryManager: def __init__(self, max_capacity: int = 100): self.max_capacity = max_capacity self._undo_stack: typing.List[HistoryEntry] = [] self._redo_stack: typing.List[HistoryEntry] = [] def push(self, state: typing.Any, description: str) -> None: """ Pushes a new state to the undo stack and clears the redo stack. If the undo stack exceeds max_capacity, the oldest state is removed. """ entry = HistoryEntry(state=state, description=description) self._undo_stack.append(entry) self._redo_stack.clear() if len(self._undo_stack) > self.max_capacity: self._undo_stack.pop(0) def undo(self, current_state: typing.Any, current_description: str = "Current State") -> typing.Optional[HistoryEntry]: """ Undoes the last action by moving the current_state to the redo stack and returning the top of the undo stack. """ if not self._undo_stack: return None redo_entry = HistoryEntry(state=current_state, description=current_description) self._redo_stack.append(redo_entry) return self._undo_stack.pop() def redo(self, current_state: typing.Any, current_description: str = "Current State") -> typing.Optional[HistoryEntry]: """ Redoes the last undone action by moving the current_state to the undo stack and returning the top of the redo stack. """ if not self._redo_stack: return None undo_entry = HistoryEntry(state=current_state, description=current_description) self._undo_stack.append(undo_entry) return self._redo_stack.pop() @property def can_undo(self) -> bool: return len(self._undo_stack) > 0 @property def can_redo(self) -> bool: return len(self._redo_stack) > 0 def get_history(self) -> typing.List[typing.Dict[str, typing.Any]]: """Returns a list of descriptions and timestamps for the undo stack.""" return [ {"description": e.description, "timestamp": e.timestamp} for e in self._undo_stack ] def jump_to_undo(self, index: int, current_state: typing.Any, current_description: str = "Before Jump") -> typing.Optional[HistoryEntry]: """ Jumps to a specific state in the undo stack by moving subsequent states and the current_state to the redo stack. """ if index < 0 or index >= len(self._undo_stack): return None # Move current state to redo self._redo_stack.append(HistoryEntry(state=current_state, description=current_description)) # Move states between index and top of undo to redo while len(self._undo_stack) > index + 1: self._redo_stack.append(self._undo_stack.pop()) return self._undo_stack.pop()