mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 10:42:25 +08:00
582 lines
23 KiB
Python
582 lines
23 KiB
Python
# Interactive Feedback MCP UI
|
|
# Developed by Fábio Ferreira (https://x.com/fabiomlferreira)
|
|
# Inspired by/related to dotcursorrules.com (https://dotcursorrules.com/)
|
|
import os
|
|
import sys
|
|
import json
|
|
import psutil
|
|
import argparse
|
|
import subprocess
|
|
import threading
|
|
import hashlib
|
|
from typing import Optional, TypedDict
|
|
|
|
from PySide6.QtWidgets import (
|
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
QLabel, QLineEdit, QPushButton, QCheckBox, QTextEdit, QGroupBox
|
|
)
|
|
from PySide6.QtCore import Qt, Signal, QObject, QTimer, QSettings
|
|
from PySide6.QtGui import QTextCursor, QIcon, QKeyEvent, QFont, QFontDatabase, QPalette, QColor
|
|
|
|
class FeedbackResult(TypedDict):
|
|
command_logs: str
|
|
interactive_feedback: str
|
|
|
|
class FeedbackConfig(TypedDict):
|
|
run_command: str
|
|
execute_automatically: bool
|
|
|
|
def set_dark_title_bar(widget: QWidget, dark_title_bar: bool) -> None:
|
|
# Ensure we're on Windows
|
|
if sys.platform != "win32":
|
|
return
|
|
|
|
from ctypes import windll, c_uint32, byref
|
|
|
|
# Get Windows build number
|
|
build_number = sys.getwindowsversion().build
|
|
if build_number < 17763: # Windows 10 1809 minimum
|
|
return
|
|
|
|
# Check if the widget's property already matches the setting
|
|
dark_prop = widget.property("DarkTitleBar")
|
|
if dark_prop is not None and dark_prop == dark_title_bar:
|
|
return
|
|
|
|
# Set the property (True if dark_title_bar != 0, False otherwise)
|
|
widget.setProperty("DarkTitleBar", dark_title_bar)
|
|
|
|
# Load dwmapi.dll and call DwmSetWindowAttribute
|
|
dwmapi = windll.dwmapi
|
|
hwnd = widget.winId() # Get the window handle
|
|
attribute = 20 if build_number >= 18985 else 19 # Use newer attribute for newer builds
|
|
c_dark_title_bar = c_uint32(dark_title_bar) # Convert to C-compatible uint32
|
|
dwmapi.DwmSetWindowAttribute(hwnd, attribute, byref(c_dark_title_bar), 4)
|
|
|
|
# HACK: Create a 1x1 pixel frameless window to force redraw
|
|
temp_widget = QWidget(None, Qt.FramelessWindowHint)
|
|
temp_widget.resize(1, 1)
|
|
temp_widget.move(widget.pos())
|
|
temp_widget.show()
|
|
temp_widget.deleteLater() # Safe deletion in Qt event loop
|
|
|
|
def get_dark_mode_palette(app: QApplication):
|
|
darkPalette = app.palette()
|
|
darkPalette.setColor(QPalette.Window, QColor(53, 53, 53))
|
|
darkPalette.setColor(QPalette.WindowText, Qt.white)
|
|
darkPalette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(127, 127, 127))
|
|
darkPalette.setColor(QPalette.Base, QColor(42, 42, 42))
|
|
darkPalette.setColor(QPalette.AlternateBase, QColor(66, 66, 66))
|
|
darkPalette.setColor(QPalette.ToolTipBase, QColor(53, 53, 53))
|
|
darkPalette.setColor(QPalette.ToolTipText, Qt.white)
|
|
darkPalette.setColor(QPalette.Text, Qt.white)
|
|
darkPalette.setColor(QPalette.Disabled, QPalette.Text, QColor(127, 127, 127))
|
|
darkPalette.setColor(QPalette.Dark, QColor(35, 35, 35))
|
|
darkPalette.setColor(QPalette.Shadow, QColor(20, 20, 20))
|
|
darkPalette.setColor(QPalette.Button, QColor(53, 53, 53))
|
|
darkPalette.setColor(QPalette.ButtonText, Qt.white)
|
|
darkPalette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(127, 127, 127))
|
|
darkPalette.setColor(QPalette.BrightText, Qt.red)
|
|
darkPalette.setColor(QPalette.Link, QColor(42, 130, 218))
|
|
darkPalette.setColor(QPalette.Highlight, QColor(42, 130, 218))
|
|
darkPalette.setColor(QPalette.Disabled, QPalette.Highlight, QColor(80, 80, 80))
|
|
darkPalette.setColor(QPalette.HighlightedText, Qt.white)
|
|
darkPalette.setColor(QPalette.Disabled, QPalette.HighlightedText, QColor(127, 127, 127))
|
|
darkPalette.setColor(QPalette.PlaceholderText, QColor(127, 127, 127))
|
|
return darkPalette
|
|
|
|
def kill_tree(process: subprocess.Popen):
|
|
killed: list[psutil.Process] = []
|
|
parent = psutil.Process(process.pid)
|
|
for proc in parent.children(recursive=True):
|
|
try:
|
|
proc.kill()
|
|
killed.append(proc)
|
|
except psutil.Error:
|
|
pass
|
|
try:
|
|
parent.kill()
|
|
except psutil.Error:
|
|
pass
|
|
killed.append(parent)
|
|
|
|
# Terminate any remaining processes
|
|
for proc in killed:
|
|
try:
|
|
if proc.is_running():
|
|
proc.terminate()
|
|
except psutil.Error:
|
|
pass
|
|
|
|
def get_user_environment() -> dict[str, str]:
|
|
if sys.platform != "win32":
|
|
return os.environ.copy()
|
|
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
|
|
# Load required DLLs
|
|
advapi32 = ctypes.WinDLL("advapi32")
|
|
userenv = ctypes.WinDLL("userenv")
|
|
kernel32 = ctypes.WinDLL("kernel32")
|
|
|
|
# Constants
|
|
TOKEN_QUERY = 0x0008
|
|
|
|
# Function prototypes
|
|
OpenProcessToken = advapi32.OpenProcessToken
|
|
OpenProcessToken.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.POINTER(wintypes.HANDLE)]
|
|
OpenProcessToken.restype = wintypes.BOOL
|
|
|
|
CreateEnvironmentBlock = userenv.CreateEnvironmentBlock
|
|
CreateEnvironmentBlock.argtypes = [ctypes.POINTER(ctypes.c_void_p), wintypes.HANDLE, wintypes.BOOL]
|
|
CreateEnvironmentBlock.restype = wintypes.BOOL
|
|
|
|
DestroyEnvironmentBlock = userenv.DestroyEnvironmentBlock
|
|
DestroyEnvironmentBlock.argtypes = [wintypes.LPVOID]
|
|
DestroyEnvironmentBlock.restype = wintypes.BOOL
|
|
|
|
GetCurrentProcess = kernel32.GetCurrentProcess
|
|
GetCurrentProcess.argtypes = []
|
|
GetCurrentProcess.restype = wintypes.HANDLE
|
|
|
|
CloseHandle = kernel32.CloseHandle
|
|
CloseHandle.argtypes = [wintypes.HANDLE]
|
|
CloseHandle.restype = wintypes.BOOL
|
|
|
|
# Get process token
|
|
token = wintypes.HANDLE()
|
|
if not OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, ctypes.byref(token)):
|
|
raise RuntimeError("Failed to open process token")
|
|
|
|
try:
|
|
# Create environment block
|
|
environment = ctypes.c_void_p()
|
|
if not CreateEnvironmentBlock(ctypes.byref(environment), token, False):
|
|
raise RuntimeError("Failed to create environment block")
|
|
|
|
try:
|
|
# Convert environment block to list of strings
|
|
result = {}
|
|
env_ptr = ctypes.cast(environment, ctypes.POINTER(ctypes.c_wchar))
|
|
offset = 0
|
|
|
|
while True:
|
|
# Get string at current offset
|
|
current_string = ""
|
|
while env_ptr[offset] != "\0":
|
|
current_string += env_ptr[offset]
|
|
offset += 1
|
|
|
|
# Skip null terminator
|
|
offset += 1
|
|
|
|
# Break if we hit double null terminator
|
|
if not current_string:
|
|
break
|
|
|
|
equal_index = current_string.index("=")
|
|
if equal_index == -1:
|
|
continue
|
|
|
|
key = current_string[:equal_index]
|
|
value = current_string[equal_index + 1:]
|
|
result[key] = value
|
|
|
|
return result
|
|
|
|
finally:
|
|
DestroyEnvironmentBlock(environment)
|
|
|
|
finally:
|
|
CloseHandle(token)
|
|
|
|
class FeedbackTextEdit(QTextEdit):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
|
|
def keyPressEvent(self, event: QKeyEvent):
|
|
if event.key() == Qt.Key_Return and event.modifiers() == Qt.ControlModifier:
|
|
# Find the parent FeedbackUI instance and call submit
|
|
parent = self.parent()
|
|
while parent and not isinstance(parent, FeedbackUI):
|
|
parent = parent.parent()
|
|
if parent:
|
|
parent._submit_feedback()
|
|
else:
|
|
super().keyPressEvent(event)
|
|
|
|
class LogSignals(QObject):
|
|
append_log = Signal(str)
|
|
|
|
class FeedbackUI(QMainWindow):
|
|
def __init__(self, project_directory: str, prompt: str):
|
|
super().__init__()
|
|
self.project_directory = project_directory
|
|
self.prompt = prompt
|
|
|
|
self.process: Optional[subprocess.Popen] = None
|
|
self.log_buffer = []
|
|
self.feedback_result = None
|
|
self.log_signals = LogSignals()
|
|
self.log_signals.append_log.connect(self._append_log)
|
|
|
|
self.setWindowTitle("Interactive Feedback MCP")
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
icon_path = os.path.join(script_dir, "images", "feedback.png")
|
|
self.setWindowIcon(QIcon(icon_path))
|
|
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
|
|
|
|
self.settings = QSettings("InteractiveFeedbackMCP", "InteractiveFeedbackMCP")
|
|
|
|
# Load general UI settings for the main window (geometry, state)
|
|
self.settings.beginGroup("MainWindow_General")
|
|
geometry = self.settings.value("geometry")
|
|
if geometry:
|
|
self.restoreGeometry(geometry)
|
|
else:
|
|
self.resize(800, 600)
|
|
screen = QApplication.primaryScreen().geometry()
|
|
x = (screen.width() - 800) // 2
|
|
y = (screen.height() - 600) // 2
|
|
self.move(x, y)
|
|
state = self.settings.value("windowState")
|
|
if state:
|
|
self.restoreState(state)
|
|
self.settings.endGroup() # End "MainWindow_General" group
|
|
|
|
# Load project-specific settings (command, auto-execute, command section visibility)
|
|
self.project_group_name = get_project_settings_group(self.project_directory)
|
|
self.settings.beginGroup(self.project_group_name)
|
|
loaded_run_command = self.settings.value("run_command", "", type=str)
|
|
loaded_execute_auto = self.settings.value("execute_automatically", False, type=bool)
|
|
command_section_visible = self.settings.value("commandSectionVisible", False, type=bool)
|
|
self.settings.endGroup() # End project-specific group
|
|
|
|
self.config: FeedbackConfig = {
|
|
"run_command": loaded_run_command,
|
|
"execute_automatically": loaded_execute_auto
|
|
}
|
|
|
|
self._create_ui() # self.config is used here to set initial values
|
|
|
|
# Set command section visibility AFTER _create_ui has created relevant widgets
|
|
self.command_group.setVisible(command_section_visible)
|
|
if command_section_visible:
|
|
self.toggle_command_button.setText("Hide Command Section")
|
|
else:
|
|
self.toggle_command_button.setText("Show Command Section")
|
|
|
|
set_dark_title_bar(self, True)
|
|
|
|
if self.config.get("execute_automatically", False):
|
|
self._run_command()
|
|
|
|
def _format_windows_path(self, path: str) -> str:
|
|
if sys.platform == "win32":
|
|
# Convert forward slashes to backslashes
|
|
path = path.replace("/", "\\")
|
|
# Capitalize drive letter if path starts with x:\
|
|
if len(path) >= 2 and path[1] == ":" and path[0].isalpha():
|
|
path = path[0].upper() + path[1:]
|
|
return path
|
|
|
|
def _create_ui(self):
|
|
central_widget = QWidget()
|
|
self.setCentralWidget(central_widget)
|
|
layout = QVBoxLayout(central_widget)
|
|
|
|
# Toggle Command Section Button
|
|
self.toggle_command_button = QPushButton("Show Command Section")
|
|
self.toggle_command_button.clicked.connect(self._toggle_command_section)
|
|
layout.addWidget(self.toggle_command_button)
|
|
|
|
# Command section
|
|
self.command_group = QGroupBox("Command")
|
|
command_layout = QVBoxLayout(self.command_group)
|
|
|
|
# Working directory label
|
|
formatted_path = self._format_windows_path(self.project_directory)
|
|
working_dir_label = QLabel(f"Working directory: {formatted_path}")
|
|
command_layout.addWidget(working_dir_label)
|
|
|
|
# Command input row
|
|
command_input_layout = QHBoxLayout()
|
|
self.command_entry = QLineEdit()
|
|
self.command_entry.setText(self.config["run_command"])
|
|
self.command_entry.returnPressed.connect(self._run_command)
|
|
self.command_entry.textChanged.connect(self._update_config)
|
|
self.run_button = QPushButton("&Run")
|
|
self.run_button.clicked.connect(self._run_command)
|
|
|
|
command_input_layout.addWidget(self.command_entry)
|
|
command_input_layout.addWidget(self.run_button)
|
|
command_layout.addLayout(command_input_layout)
|
|
|
|
# Auto-execute and save config row
|
|
auto_layout = QHBoxLayout()
|
|
self.auto_check = QCheckBox("Execute automatically on next run")
|
|
self.auto_check.setChecked(self.config.get("execute_automatically", False))
|
|
self.auto_check.stateChanged.connect(self._update_config)
|
|
|
|
save_button = QPushButton("&Save Configuration")
|
|
save_button.clicked.connect(self._save_config)
|
|
|
|
auto_layout.addWidget(self.auto_check)
|
|
auto_layout.addStretch()
|
|
auto_layout.addWidget(save_button)
|
|
command_layout.addLayout(auto_layout)
|
|
|
|
# Console section (now part of command_group)
|
|
console_group = QGroupBox("Console")
|
|
console_layout_internal = QVBoxLayout(console_group)
|
|
console_group.setMinimumHeight(200)
|
|
|
|
# Log text area
|
|
self.log_text = QTextEdit()
|
|
self.log_text.setReadOnly(True)
|
|
font = QFont(QFontDatabase.systemFont(QFontDatabase.FixedFont))
|
|
font.setPointSize(9)
|
|
self.log_text.setFont(font)
|
|
console_layout_internal.addWidget(self.log_text)
|
|
|
|
# Clear button
|
|
button_layout = QHBoxLayout()
|
|
self.clear_button = QPushButton("&Clear")
|
|
self.clear_button.clicked.connect(self.clear_logs)
|
|
button_layout.addStretch()
|
|
button_layout.addWidget(self.clear_button)
|
|
console_layout_internal.addLayout(button_layout)
|
|
|
|
command_layout.addWidget(console_group)
|
|
|
|
self.command_group.setVisible(False)
|
|
layout.addWidget(self.command_group)
|
|
|
|
# Feedback section with adjusted height
|
|
self.feedback_group = QGroupBox("Feedback")
|
|
feedback_layout = QVBoxLayout(self.feedback_group)
|
|
|
|
# Short description label (from self.prompt)
|
|
self.description_label = QLabel(self.prompt)
|
|
self.description_label.setWordWrap(True)
|
|
feedback_layout.addWidget(self.description_label)
|
|
|
|
self.feedback_text = FeedbackTextEdit()
|
|
font_metrics = self.feedback_text.fontMetrics()
|
|
row_height = font_metrics.height()
|
|
# Calculate height for 5 lines + some padding for margins
|
|
padding = self.feedback_text.contentsMargins().top() + self.feedback_text.contentsMargins().bottom() + 5 # 5 is extra vertical padding
|
|
self.feedback_text.setMinimumHeight(5 * row_height + padding)
|
|
|
|
self.feedback_text.setPlaceholderText("Enter your feedback here (Ctrl+Enter to submit)")
|
|
submit_button = QPushButton("&Send Feedback (Ctrl+Enter)")
|
|
submit_button.clicked.connect(self._submit_feedback)
|
|
|
|
feedback_layout.addWidget(self.feedback_text)
|
|
feedback_layout.addWidget(submit_button)
|
|
|
|
# Set minimum height for feedback_group to accommodate its contents
|
|
# This will be based on the description label and the 5-line feedback_text
|
|
self.feedback_group.setMinimumHeight(self.description_label.sizeHint().height() + self.feedback_text.minimumHeight() + submit_button.sizeHint().height() + feedback_layout.spacing() * 2 + feedback_layout.contentsMargins().top() + feedback_layout.contentsMargins().bottom() + 10) # 10 for extra padding
|
|
|
|
# Add widgets in a specific order
|
|
layout.addWidget(self.feedback_group)
|
|
|
|
# Credits/Contact Label
|
|
contact_label = QLabel('Need to improve? Contact Fábio Ferreira on <a href="https://x.com/fabiomlferreira">X.com</a> or visit <a href="https://dotcursorrules.com/">dotcursorrules.com</a>')
|
|
contact_label.setOpenExternalLinks(True)
|
|
contact_label.setAlignment(Qt.AlignCenter)
|
|
# Optionally, make font a bit smaller and less prominent
|
|
# contact_label_font = contact_label.font()
|
|
# contact_label_font.setPointSize(contact_label_font.pointSize() - 1)
|
|
# contact_label.setFont(contact_label_font)
|
|
contact_label.setStyleSheet("font-size: 9pt; color: #cccccc;") # Light gray for dark theme
|
|
layout.addWidget(contact_label)
|
|
|
|
def _toggle_command_section(self):
|
|
is_visible = self.command_group.isVisible()
|
|
self.command_group.setVisible(not is_visible)
|
|
if not is_visible:
|
|
self.toggle_command_button.setText("Hide Command Section")
|
|
else:
|
|
self.toggle_command_button.setText("Show Command Section")
|
|
|
|
# Immediately save the visibility state for this project
|
|
self.settings.beginGroup(self.project_group_name)
|
|
self.settings.setValue("commandSectionVisible", self.command_group.isVisible())
|
|
self.settings.endGroup()
|
|
|
|
# Adjust window height only
|
|
new_height = self.centralWidget().sizeHint().height()
|
|
if self.command_group.isVisible() and self.command_group.layout().sizeHint().height() > 0 :
|
|
# if command group became visible and has content, ensure enough height
|
|
min_content_height = self.command_group.layout().sizeHint().height() + self.feedback_group.minimumHeight() + self.toggle_command_button.height() + layout().spacing() * 2
|
|
new_height = max(new_height, min_content_height)
|
|
|
|
current_width = self.width()
|
|
self.resize(current_width, new_height)
|
|
|
|
def _update_config(self):
|
|
self.config["run_command"] = self.command_entry.text()
|
|
self.config["execute_automatically"] = self.auto_check.isChecked()
|
|
|
|
def _append_log(self, text: str):
|
|
self.log_buffer.append(text)
|
|
self.log_text.append(text.rstrip())
|
|
cursor = self.log_text.textCursor()
|
|
cursor.movePosition(QTextCursor.End)
|
|
self.log_text.setTextCursor(cursor)
|
|
|
|
def _check_process_status(self):
|
|
if self.process and self.process.poll() is not None:
|
|
# Process has terminated
|
|
exit_code = self.process.poll()
|
|
self._append_log(f"\nProcess exited with code {exit_code}\n")
|
|
self.run_button.setText("&Run")
|
|
self.process = None
|
|
self.activateWindow()
|
|
self.feedback_text.setFocus()
|
|
|
|
def _run_command(self):
|
|
if self.process:
|
|
kill_tree(self.process)
|
|
self.process = None
|
|
self.run_button.setText("&Run")
|
|
return
|
|
|
|
# Clear the log buffer but keep UI logs visible
|
|
self.log_buffer = []
|
|
|
|
command = self.command_entry.text()
|
|
if not command:
|
|
self._append_log("Please enter a command to run\n")
|
|
return
|
|
|
|
self._append_log(f"$ {command}\n")
|
|
self.run_button.setText("Sto&p")
|
|
|
|
try:
|
|
self.process = subprocess.Popen(
|
|
command,
|
|
shell=True,
|
|
cwd=self.project_directory,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
env=get_user_environment(),
|
|
text=True,
|
|
bufsize=1,
|
|
encoding="utf-8",
|
|
errors="ignore",
|
|
close_fds=True,
|
|
)
|
|
|
|
def read_output(pipe):
|
|
for line in iter(pipe.readline, ""):
|
|
self.log_signals.append_log.emit(line)
|
|
|
|
threading.Thread(
|
|
target=read_output,
|
|
args=(self.process.stdout,),
|
|
daemon=True
|
|
).start()
|
|
|
|
threading.Thread(
|
|
target=read_output,
|
|
args=(self.process.stderr,),
|
|
daemon=True
|
|
).start()
|
|
|
|
# Start process status checking
|
|
self.status_timer = QTimer()
|
|
self.status_timer.timeout.connect(self._check_process_status)
|
|
self.status_timer.start(100) # Check every 100ms
|
|
|
|
except Exception as e:
|
|
self._append_log(f"Error running command: {str(e)}\n")
|
|
self.run_button.setText("&Run")
|
|
|
|
def _submit_feedback(self):
|
|
self.feedback_result = FeedbackResult(
|
|
logs="".join(self.log_buffer),
|
|
interactive_feedback=self.feedback_text.toPlainText().strip(),
|
|
)
|
|
self.close()
|
|
|
|
def clear_logs(self):
|
|
self.log_buffer = []
|
|
self.log_text.clear()
|
|
|
|
def _save_config(self):
|
|
# Save run_command and execute_automatically to QSettings under project group
|
|
self.settings.beginGroup(self.project_group_name)
|
|
self.settings.setValue("run_command", self.config["run_command"])
|
|
self.settings.setValue("execute_automatically", self.config["execute_automatically"])
|
|
self.settings.endGroup()
|
|
self._append_log("Configuration saved for this project.\n")
|
|
|
|
def closeEvent(self, event):
|
|
# Save general UI settings for the main window (geometry, state)
|
|
self.settings.beginGroup("MainWindow_General")
|
|
self.settings.setValue("geometry", self.saveGeometry())
|
|
self.settings.setValue("windowState", self.saveState())
|
|
self.settings.endGroup()
|
|
|
|
# Save project-specific command section visibility (this is now slightly redundant due to immediate save in toggle, but harmless)
|
|
self.settings.beginGroup(self.project_group_name)
|
|
self.settings.setValue("commandSectionVisible", self.command_group.isVisible())
|
|
self.settings.endGroup()
|
|
|
|
if self.process:
|
|
kill_tree(self.process)
|
|
super().closeEvent(event)
|
|
|
|
def run(self) -> FeedbackResult:
|
|
self.show()
|
|
QApplication.instance().exec()
|
|
|
|
if self.process:
|
|
kill_tree(self.process)
|
|
|
|
if not self.feedback_result:
|
|
return FeedbackResult(logs="".join(self.log_buffer), interactive_feedback="")
|
|
|
|
return self.feedback_result
|
|
|
|
def get_project_settings_group(project_dir: str) -> str:
|
|
# Create a safe, unique group name from the project directory path
|
|
# Using only the last component + hash of full path to keep it somewhat readable but unique
|
|
basename = os.path.basename(os.path.normpath(project_dir))
|
|
full_hash = hashlib.md5(project_dir.encode('utf-8')).hexdigest()[:8]
|
|
return f"{basename}_{full_hash}"
|
|
|
|
def feedback_ui(project_directory: str, prompt: str, output_file: Optional[str] = None) -> Optional[FeedbackResult]:
|
|
app = QApplication.instance() or QApplication()
|
|
app.setPalette(get_dark_mode_palette(app))
|
|
app.setStyle("Fusion")
|
|
ui = FeedbackUI(project_directory, prompt)
|
|
result = ui.run()
|
|
|
|
if output_file and result:
|
|
# Ensure the directory exists
|
|
os.makedirs(os.path.dirname(output_file) if os.path.dirname(output_file) else ".", exist_ok=True)
|
|
# Save the result to the output file
|
|
with open(output_file, "w") as f:
|
|
json.dump(result, f)
|
|
return None
|
|
|
|
return result
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Run the feedback UI")
|
|
parser.add_argument("--project-directory", default=os.getcwd(), help="The project directory to run the command in")
|
|
parser.add_argument("--prompt", default="I implemented the changes you requested.", help="The prompt to show to the user")
|
|
parser.add_argument("--output-file", help="Path to save the feedback result as JSON")
|
|
args = parser.parse_args()
|
|
|
|
result = feedback_ui(args.project_directory, args.prompt, args.output_file)
|
|
if result:
|
|
print(f"\nLogs collected: \n{result['logs']}")
|
|
print(f"\nFeedback received:\n{result['interactive_feedback']}")
|
|
sys.exit(0)
|