chore(fix): Resolve regressions in history logic, track startup, and GUI performance
This commit is contained in:
+99
-73
@@ -108,7 +108,8 @@ class App:
|
||||
"""
|
||||
self.controller = app_controller.AppController()
|
||||
self.controller._app = self
|
||||
from src import history
|
||||
from src import history, performance_monitor
|
||||
self.perf_monitor = performance_monitor.PerformanceMonitor()
|
||||
self.history = history.HistoryManager(max_capacity=100)
|
||||
self._last_ui_snapshot: Optional[history.UISnapshot] = None
|
||||
self._snapshot_timer: float = 0.0
|
||||
@@ -117,7 +118,7 @@ class App:
|
||||
self._is_applying_snapshot: bool = False
|
||||
|
||||
self.controller.init_state()
|
||||
self.workspace_manager = workspace_manager.WorkspaceManager(project_root=self.active_project_root)
|
||||
self.workspace_manager = workspace_manager.WorkspaceManager(project_root=self.controller.active_project_root)
|
||||
self.workspace_profiles = self.workspace_manager.load_all_profiles()
|
||||
self.show_windows.setdefault("Diagnostics", False)
|
||||
self.controller.start_services(self)
|
||||
@@ -133,7 +134,9 @@ class App:
|
||||
})
|
||||
def simulate_save_preset(name: str):
|
||||
from src import models
|
||||
self.files = [models.FileItem(path='test.py')]
|
||||
item = models.FileItem(path='test.py')
|
||||
self.files = [item]
|
||||
self.context_files = [item]
|
||||
self.screenshots = ['test.png']
|
||||
self.save_context_preset(name)
|
||||
self.controller._predefined_callbacks['simulate_save_preset'] = simulate_save_preset
|
||||
@@ -328,6 +331,7 @@ class App:
|
||||
auto_add_history=self.ui_auto_add_history,
|
||||
disc_entries=copy.deepcopy(self.disc_entries),
|
||||
files=[f.to_dict() if hasattr(f, 'to_dict') else f for f in self.files],
|
||||
context_files=[f.to_dict() if hasattr(f, 'to_dict') else f for f in self.context_files],
|
||||
screenshots=list(self.screenshots)
|
||||
)
|
||||
|
||||
@@ -354,6 +358,13 @@ class App:
|
||||
else:
|
||||
self.files.append(models.FileItem(path=str(f)))
|
||||
|
||||
self.context_files = []
|
||||
for f in snapshot.context_files:
|
||||
if isinstance(f, dict):
|
||||
self.context_files.append(models.FileItem.from_dict(f))
|
||||
else:
|
||||
self.context_files.append(models.FileItem(path=str(f)))
|
||||
|
||||
self.screenshots = list(snapshot.screenshots)
|
||||
self._last_ui_snapshot = snapshot # Update last snapshot to avoid immediate re-push
|
||||
finally:
|
||||
@@ -1639,67 +1650,62 @@ class App:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
|
||||
|
||||
def _handle_history_logic(self) -> None:
|
||||
# Skip history tracking only during active AI thinking/tool execution
|
||||
is_thinking = self.ai_status in ["sending...", "streaming...", "running powershell...", "fetching url...", "searching web..."]
|
||||
if self._is_applying_snapshot or is_thinking:
|
||||
return
|
||||
io = imgui.get_io()
|
||||
|
||||
# 1. Hotkey handling (Undo: Ctrl+Z, Redo: Ctrl+Y or Ctrl+Shift+Z)
|
||||
ctrl = io.key_ctrl
|
||||
shift = io.key_shift
|
||||
|
||||
if ctrl:
|
||||
if imgui.is_key_pressed(imgui.Key.z):
|
||||
if shift:
|
||||
self._handle_redo()
|
||||
else:
|
||||
self._handle_undo()
|
||||
elif imgui.is_key_pressed(imgui.Key.y):
|
||||
self._handle_redo()
|
||||
|
||||
# 2. Debounced snapshotting
|
||||
current = self._take_snapshot()
|
||||
if self._last_ui_snapshot is None:
|
||||
self._last_ui_snapshot = current
|
||||
"""
|
||||
Logic for capturing UI state for undo/redo.
|
||||
"""
|
||||
if self._is_applying_snapshot:
|
||||
return
|
||||
|
||||
# Compare only core fields for performance
|
||||
changed = (
|
||||
current.ai_input != self._last_ui_snapshot.ai_input or
|
||||
current.project_system_prompt != self._last_ui_snapshot.project_system_prompt or
|
||||
current.global_system_prompt != self._last_ui_snapshot.global_system_prompt or
|
||||
current.base_system_prompt != self._last_ui_snapshot.base_system_prompt or
|
||||
current.use_default_base_prompt != self._last_ui_snapshot.use_default_base_prompt or
|
||||
abs(current.temperature - self._last_ui_snapshot.temperature) > 1e-5 or
|
||||
abs(current.top_p - self._last_ui_snapshot.top_p) > 1e-5 or
|
||||
current.max_tokens != self._last_ui_snapshot.max_tokens or
|
||||
current.auto_add_history != self._last_ui_snapshot.auto_add_history or
|
||||
len(current.disc_entries) != len(self._last_ui_snapshot.disc_entries) or
|
||||
len(current.files) != len(self._last_ui_snapshot.files) or
|
||||
len(current.screenshots) != len(self._last_ui_snapshot.screenshots)
|
||||
)
|
||||
|
||||
if not changed and len(current.disc_entries) > 0:
|
||||
if current.disc_entries[-1].get('content') != self._last_ui_snapshot.disc_entries[-1].get('content'):
|
||||
changed = True
|
||||
try:
|
||||
# 2. Debounced snapshotting
|
||||
current = self._take_snapshot()
|
||||
if self._last_ui_snapshot is None:
|
||||
self._last_ui_snapshot = current
|
||||
return
|
||||
|
||||
if changed:
|
||||
if not self._pending_snapshot:
|
||||
self._pending_snapshot = True
|
||||
self._snapshot_timer = time.time()
|
||||
# Capture state BEFORE current change
|
||||
self._state_to_push = self._last_ui_snapshot
|
||||
else:
|
||||
# Reset timer for settle debounce
|
||||
self._snapshot_timer = time.time()
|
||||
# Compare only core fields for performance
|
||||
changed = (
|
||||
current.ai_input != self._last_ui_snapshot.ai_input or
|
||||
current.project_system_prompt != self._last_ui_snapshot.project_system_prompt or
|
||||
current.global_system_prompt != self._last_ui_snapshot.global_system_prompt or
|
||||
current.base_system_prompt != self._last_ui_snapshot.base_system_prompt or
|
||||
current.use_default_base_prompt != self._last_ui_snapshot.use_default_base_prompt or
|
||||
abs(current.temperature - self._last_ui_snapshot.temperature) > 1e-5 or
|
||||
abs(current.top_p - self._last_ui_snapshot.top_p) > 1e-5 or
|
||||
current.max_tokens != self._last_ui_snapshot.max_tokens or
|
||||
current.auto_add_history != self._last_ui_snapshot.auto_add_history or
|
||||
len(current.disc_entries) != len(self._last_ui_snapshot.disc_entries) or
|
||||
len(current.files) != len(self._last_ui_snapshot.files) or
|
||||
len(current.context_files) != len(self._last_ui_snapshot.context_files) or
|
||||
len(current.screenshots) != len(self._last_ui_snapshot.screenshots)
|
||||
)
|
||||
|
||||
self._last_ui_snapshot = current
|
||||
if not changed and len(current.disc_entries) > 0:
|
||||
if current.disc_entries[-1].get('content') != self._last_ui_snapshot.disc_entries[-1].get('content'):
|
||||
changed = True
|
||||
|
||||
if self._pending_snapshot and (time.time() - self._snapshot_timer > self._snapshot_debounce):
|
||||
if self._state_to_push:
|
||||
self.history.push(self._state_to_push, "UI State Change")
|
||||
self._pending_snapshot = False
|
||||
if changed:
|
||||
if not self._pending_snapshot:
|
||||
self._pending_snapshot = True
|
||||
self._snapshot_timer = time.time()
|
||||
# Capture state BEFORE current change
|
||||
self._state_to_push = self._last_ui_snapshot
|
||||
else:
|
||||
# Reset timer for settle debounce
|
||||
self._snapshot_timer = time.time()
|
||||
|
||||
self._last_ui_snapshot = current
|
||||
|
||||
if self._pending_snapshot and (time.time() - self._snapshot_timer > self._snapshot_debounce):
|
||||
if self._state_to_push:
|
||||
self.history.push(self._state_to_push, "UI Update")
|
||||
self._state_to_push = None
|
||||
self._pending_snapshot = False
|
||||
except Exception as e:
|
||||
import sys, traceback
|
||||
sys.stderr.write(f"[DEBUG History] ERROR in _handle_history_logic: {e}\n")
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
|
||||
def _render_base_prompt_diff_modal(self) -> None:
|
||||
if not getattr(self.controller, "_show_base_prompt_diff_modal", False):
|
||||
@@ -2983,22 +2989,42 @@ class App:
|
||||
def _render_context_composition_panel(self) -> None:
|
||||
if not hasattr(self, '_file_stats_cache'):
|
||||
self._file_stats_cache = {}
|
||||
|
||||
total_lines = 0
|
||||
total_ast = 0
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
mtime = os.path.getmtime(f_path) if os.path.exists(f_path) else 0
|
||||
cache_key = f"{f_path}_{mtime}"
|
||||
if cache_key not in self._file_stats_cache:
|
||||
self._file_stats_cache[cache_key] = aggregate.compute_file_stats(f_path)
|
||||
stats = self._file_stats_cache[cache_key]
|
||||
total_lines += stats.get("lines", 0)
|
||||
total_ast += stats.get("ast_elements", 0)
|
||||
if not hasattr(self, '_file_stats_queue'):
|
||||
self._file_stats_queue = []
|
||||
if not hasattr(self, '_file_stats_worker_active'):
|
||||
self._file_stats_worker_active = False
|
||||
|
||||
if imgui.collapsing_header("Context Composition"):
|
||||
#region: Batch Action Bar
|
||||
imgui.text("Batch:")
|
||||
total_lines = 0
|
||||
total_ast = 0
|
||||
|
||||
missing_keys = []
|
||||
for f in self.context_files:
|
||||
f_path = f.path if hasattr(f, "path") else str(f)
|
||||
mtime = os.path.getmtime(f_path) if os.path.exists(f_path) else 0
|
||||
cache_key = f"{f_path}_{mtime}"
|
||||
if cache_key not in self._file_stats_cache:
|
||||
missing_keys.append((f_path, cache_key))
|
||||
else:
|
||||
stats = self._file_stats_cache[cache_key]
|
||||
total_lines += stats.get("lines", 0)
|
||||
total_ast += stats.get("ast_elements", 0)
|
||||
|
||||
# Process one missing key per frame or spawn a worker
|
||||
if missing_keys and not self._file_stats_worker_active:
|
||||
def _stats_worker():
|
||||
self._file_stats_worker_active = True
|
||||
try:
|
||||
import threading
|
||||
for path, key in missing_keys[:10]: # Process small batches
|
||||
self._file_stats_cache[key] = aggregate.compute_file_stats(path)
|
||||
finally:
|
||||
self._file_stats_worker_active = False
|
||||
|
||||
import threading
|
||||
threading.Thread(target=_stats_worker, daemon=True).start()
|
||||
|
||||
#region: Batch Action Bar imgui.text("Batch:")
|
||||
imgui.same_line()
|
||||
for mode in ["full", "summary", "skeleton", "outline", "masked", "none"]:
|
||||
if imgui.button(f"{mode.capitalize()}##batch"):
|
||||
|
||||
Reference in New Issue
Block a user