Convert TUI to PySide6 desktop GUI
Major UI overhaul replacing the urwid-based Terminal UI with a modern PySide6 desktop application. New GUI features: - Sidebar navigation (My Tools, Registry, Providers) - Tool Builder with visual form for creating/editing tools - Registry browser with search and one-click install - Provider management page - Connect dialog for account pairing - Publish dialog for sharing tools - Keyboard shortcuts (Ctrl+N, Ctrl+S, Ctrl+R, Ctrl+1/2/3, Escape, Ctrl+Q) - Window geometry persistence (remembers size/position) - Modern clean stylesheet Removed: - ui.py, ui_snack.py, ui_registry.py, ui_urwid.py - ui_urwid/ directory (urwid TUI implementation) Updated: - pyproject.toml: PySide6 now required, removed urwid - CLI entry points to launch GUI - All documentation (README, CLAUDE.md, AGENTS.md, wiki) Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
"""Step editor dialogs."""
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QFormLayout, QLineEdit,
|
||||
QComboBox, QPushButton, QHBoxLayout, QLabel,
|
||||
QPlainTextEdit
|
||||
)
|
||||
|
||||
from ...tool import PromptStep, CodeStep
|
||||
from ...providers import load_providers
|
||||
|
||||
|
||||
class PromptStepDialog(QDialog):
|
||||
"""Dialog for editing prompt steps."""
|
||||
|
||||
def __init__(self, parent, step: PromptStep = None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Edit Prompt Step" if step else "Add Prompt Step")
|
||||
self.setMinimumSize(500, 400)
|
||||
self._step = step
|
||||
self._setup_ui()
|
||||
|
||||
if step:
|
||||
self._load_step(step)
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Set up the UI."""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(16)
|
||||
|
||||
# Form
|
||||
form = QFormLayout()
|
||||
form.setSpacing(12)
|
||||
|
||||
# Provider selection
|
||||
self.provider_combo = QComboBox()
|
||||
providers = load_providers()
|
||||
for provider in sorted(providers, key=lambda p: p.name):
|
||||
self.provider_combo.addItem(provider.name)
|
||||
# Add common defaults if not present
|
||||
for default in ["claude", "gpt", "mock"]:
|
||||
if self.provider_combo.findText(default) < 0:
|
||||
self.provider_combo.addItem(default)
|
||||
form.addRow("Provider:", self.provider_combo)
|
||||
|
||||
# Output variable
|
||||
self.output_input = QLineEdit()
|
||||
self.output_input.setPlaceholderText("response")
|
||||
self.output_input.setText("response")
|
||||
form.addRow("Output variable:", self.output_input)
|
||||
|
||||
layout.addLayout(form)
|
||||
|
||||
# Prompt text
|
||||
prompt_label = QLabel("Prompt template:")
|
||||
layout.addWidget(prompt_label)
|
||||
|
||||
self.prompt_input = QPlainTextEdit()
|
||||
self.prompt_input.setPlaceholderText(
|
||||
"Enter your prompt here...\n\n"
|
||||
"Use {input} for stdin input.\n"
|
||||
"Use {variable} for argument values.\n"
|
||||
"Use {prev_output} for previous step output."
|
||||
)
|
||||
layout.addWidget(self.prompt_input, 1)
|
||||
|
||||
# Buttons
|
||||
buttons = QHBoxLayout()
|
||||
buttons.addStretch()
|
||||
|
||||
self.btn_cancel = QPushButton("Cancel")
|
||||
self.btn_cancel.setObjectName("secondary")
|
||||
self.btn_cancel.clicked.connect(self.reject)
|
||||
buttons.addWidget(self.btn_cancel)
|
||||
|
||||
self.btn_ok = QPushButton("OK")
|
||||
self.btn_ok.clicked.connect(self._validate_and_accept)
|
||||
buttons.addWidget(self.btn_ok)
|
||||
|
||||
layout.addLayout(buttons)
|
||||
|
||||
def _load_step(self, step: PromptStep):
|
||||
"""Load step data into form."""
|
||||
idx = self.provider_combo.findText(step.provider)
|
||||
if idx >= 0:
|
||||
self.provider_combo.setCurrentIndex(idx)
|
||||
else:
|
||||
self.provider_combo.setCurrentText(step.provider)
|
||||
|
||||
self.output_input.setText(step.output_var)
|
||||
self.prompt_input.setPlainText(step.prompt)
|
||||
|
||||
def _validate_and_accept(self):
|
||||
"""Validate and accept."""
|
||||
prompt = self.prompt_input.toPlainText().strip()
|
||||
if not prompt:
|
||||
self.prompt_input.setFocus()
|
||||
return
|
||||
|
||||
output = self.output_input.text().strip()
|
||||
if not output:
|
||||
self.output_input.setFocus()
|
||||
return
|
||||
|
||||
self.accept()
|
||||
|
||||
def get_step(self) -> PromptStep:
|
||||
"""Get the step from form data."""
|
||||
return PromptStep(
|
||||
prompt=self.prompt_input.toPlainText(),
|
||||
provider=self.provider_combo.currentText(),
|
||||
output_var=self.output_input.text().strip()
|
||||
)
|
||||
|
||||
|
||||
class CodeStepDialog(QDialog):
|
||||
"""Dialog for editing code steps."""
|
||||
|
||||
def __init__(self, parent, step: CodeStep = None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Edit Code Step" if step else "Add Code Step")
|
||||
self.setMinimumSize(600, 500)
|
||||
self._step = step
|
||||
self._setup_ui()
|
||||
|
||||
if step:
|
||||
self._load_step(step)
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Set up the UI."""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(16)
|
||||
|
||||
# Form
|
||||
form = QFormLayout()
|
||||
form.setSpacing(12)
|
||||
|
||||
# Output variable
|
||||
self.output_input = QLineEdit()
|
||||
self.output_input.setPlaceholderText("result")
|
||||
self.output_input.setText("result")
|
||||
form.addRow("Output variable:", self.output_input)
|
||||
|
||||
layout.addLayout(form)
|
||||
|
||||
# Code editor
|
||||
code_label = QLabel("Code:")
|
||||
layout.addWidget(code_label)
|
||||
|
||||
self.code_input = QPlainTextEdit()
|
||||
self.code_input.setPlaceholderText(
|
||||
"# Python code here\n"
|
||||
"# Access input with: input_text\n"
|
||||
"# Access args with their variable names\n"
|
||||
"# Access previous step outputs with their variable names\n\n"
|
||||
"result = input_text.upper()"
|
||||
)
|
||||
font = self.code_input.font()
|
||||
font.setFamily("Courier New, Consolas, monospace")
|
||||
self.code_input.setFont(font)
|
||||
layout.addWidget(self.code_input, 1)
|
||||
|
||||
# Help text
|
||||
help_text = QLabel(
|
||||
"The code will be executed with variables in scope. "
|
||||
"The last expression or the output variable will be captured."
|
||||
)
|
||||
help_text.setStyleSheet("color: #718096; font-size: 11px;")
|
||||
help_text.setWordWrap(True)
|
||||
layout.addWidget(help_text)
|
||||
|
||||
# Buttons
|
||||
buttons = QHBoxLayout()
|
||||
buttons.addStretch()
|
||||
|
||||
self.btn_cancel = QPushButton("Cancel")
|
||||
self.btn_cancel.setObjectName("secondary")
|
||||
self.btn_cancel.clicked.connect(self.reject)
|
||||
buttons.addWidget(self.btn_cancel)
|
||||
|
||||
self.btn_ok = QPushButton("OK")
|
||||
self.btn_ok.clicked.connect(self._validate_and_accept)
|
||||
buttons.addWidget(self.btn_ok)
|
||||
|
||||
layout.addLayout(buttons)
|
||||
|
||||
def _load_step(self, step: CodeStep):
|
||||
"""Load step data into form."""
|
||||
self.output_input.setText(step.output_var)
|
||||
self.code_input.setPlainText(step.code)
|
||||
|
||||
def _validate_and_accept(self):
|
||||
"""Validate and accept."""
|
||||
code = self.code_input.toPlainText().strip()
|
||||
if not code:
|
||||
self.code_input.setFocus()
|
||||
return
|
||||
|
||||
output = self.output_input.text().strip()
|
||||
if not output:
|
||||
self.output_input.setFocus()
|
||||
return
|
||||
|
||||
self.accept()
|
||||
|
||||
def get_step(self) -> CodeStep:
|
||||
"""Get the step from form data."""
|
||||
return CodeStep(
|
||||
code=self.code_input.toPlainText(),
|
||||
output_var=self.output_input.text().strip()
|
||||
)
|
||||
Reference in New Issue
Block a user