132 lines
4.2 KiB
Python
132 lines
4.2 KiB
Python
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()
|