feat(tier4): Integrate patch generation into GUI workflow
- Add patch_callback parameter throughout the tool execution chain - Add _render_patch_modal() to gui_2.py with colored diff display - Add patch modal state variables to App.__init__ - Add request_patch_from_tier4() to trigger patch generation - Add run_tier4_patch_callback() to ai_client.py - Update shell_runner to accept and execute patch_callback - Diff colors: green for additions, red for deletions, cyan for headers - 36 tests passing
This commit is contained in:
84
src/gui_2.py
84
src/gui_2.py
@@ -1,4 +1,4 @@
|
||||
# gui_2.py
|
||||
# gui_2.py
|
||||
from __future__ import annotations
|
||||
import tomli_w
|
||||
import time
|
||||
@@ -114,6 +114,11 @@ class App:
|
||||
self._tool_log_dirty: bool = True
|
||||
self._last_ui_focus_agent: Optional[str] = None
|
||||
self._log_registry: Optional[log_registry.LogRegistry] = None
|
||||
# Patch viewer state for Tier 4 auto-patching
|
||||
self._pending_patch_text: Optional[str] = None
|
||||
self._pending_patch_files: list[str] = []
|
||||
self._show_patch_modal: bool = False
|
||||
self._patch_error_message: Optional[str] = None
|
||||
|
||||
def _handle_approve_tool(self, user_data=None) -> None:
|
||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||
@@ -254,6 +259,7 @@ class App:
|
||||
self._process_pending_gui_tasks()
|
||||
self._process_pending_history_adds()
|
||||
self._render_track_proposal_modal()
|
||||
self._render_patch_modal()
|
||||
# Auto-save (every 60s)
|
||||
now = time.time()
|
||||
if now - self._last_autosave >= self._autosave_interval:
|
||||
@@ -873,6 +879,82 @@ class App:
|
||||
imgui.close_current_popup()
|
||||
imgui.end_popup()
|
||||
|
||||
def _render_patch_modal(self) -> None:
|
||||
if not self._show_patch_modal:
|
||||
return
|
||||
imgui.open_popup("Apply Patch?")
|
||||
if imgui.begin_popup_modal("Apply Patch?", True, imgui.ImVec2(600, 500))[0]:
|
||||
imgui.text_colored(imgui.ImVec4(1, 0.9, 0.3, 1), "Tier 4 QA Generated a Patch")
|
||||
imgui.separator()
|
||||
if self._pending_patch_files:
|
||||
imgui.text("Files to modify:")
|
||||
for f in self._pending_patch_files:
|
||||
imgui.text(f" - {f}")
|
||||
imgui.separator()
|
||||
if self._patch_error_message:
|
||||
imgui.text_colored(imgui.ImVec4(1, 0.3, 0.3, 1), f"Error: {self._patch_error_message}")
|
||||
imgui.separator()
|
||||
imgui.text("Diff Preview:")
|
||||
imgui.begin_child("patch_diff_scroll", imgui.ImVec2(-1, 280), True)
|
||||
if self._pending_patch_text:
|
||||
diff_lines = self._pending_patch_text.split("\n")
|
||||
for line in diff_lines:
|
||||
if line.startswith("+++") or line.startswith("---") or line.startswith("@@"):
|
||||
imgui.text_colored(imgui.ImVec4(0.3, 0.7, 1, 1), line)
|
||||
elif line.startswith("+"):
|
||||
imgui.text_colored(imgui.ImVec4(0.2, 0.9, 0.2, 1), line)
|
||||
elif line.startswith("-"):
|
||||
imgui.text_colored(imgui.ImVec4(0.9, 0.2, 0.2, 1), line)
|
||||
else:
|
||||
imgui.text(line)
|
||||
imgui.end_child()
|
||||
imgui.separator()
|
||||
if imgui.button("Apply Patch", imgui.ImVec2(150, 0)):
|
||||
self._apply_pending_patch()
|
||||
imgui.same_line()
|
||||
if imgui.button("Reject", imgui.ImVec2(150, 0)):
|
||||
self._show_patch_modal = False
|
||||
self._pending_patch_text = None
|
||||
self._pending_patch_files = []
|
||||
self._patch_error_message = None
|
||||
imgui.close_current_popup()
|
||||
imgui.end_popup()
|
||||
|
||||
def _apply_pending_patch(self) -> None:
|
||||
if not self._pending_patch_text:
|
||||
self._patch_error_message = "No patch to apply"
|
||||
return
|
||||
try:
|
||||
from src.diff_viewer import apply_patch_to_file
|
||||
base_dir = str(self.controller.current_project_dir) if hasattr(self.controller, 'current_project_dir') else "."
|
||||
success, msg = apply_patch_to_file(self._pending_patch_text, base_dir)
|
||||
if success:
|
||||
self._show_patch_modal = False
|
||||
self._pending_patch_text = None
|
||||
self._pending_patch_files = []
|
||||
self._patch_error_message = None
|
||||
imgui.close_current_popup()
|
||||
else:
|
||||
self._patch_error_message = msg
|
||||
except Exception as e:
|
||||
self._patch_error_message = str(e)
|
||||
|
||||
def request_patch_from_tier4(self, error: str, file_context: str) -> None:
|
||||
try:
|
||||
from src import ai_client
|
||||
from src.diff_viewer import parse_diff
|
||||
patch_text = ai_client.run_tier4_patch_generation(error, file_context)
|
||||
if patch_text and "---" in patch_text and "+++" in patch_text:
|
||||
diff_files = parse_diff(patch_text)
|
||||
file_paths = [df.old_path for df in diff_files]
|
||||
self._pending_patch_text = patch_text
|
||||
self._pending_patch_files = file_paths
|
||||
self._show_patch_modal = True
|
||||
else:
|
||||
self._patch_error_message = patch_text or "No patch generated"
|
||||
except Exception as e:
|
||||
self._patch_error_message = str(e)
|
||||
|
||||
def _render_log_management(self) -> None:
|
||||
exp, opened = imgui.begin("Log Management", self.show_windows["Log Management"])
|
||||
self.show_windows["Log Management"] = bool(opened)
|
||||
|
||||
Reference in New Issue
Block a user