Add Help menu and contextual tooltips to GUI

- Add Help menu with quick guides: Getting Started, Create Tool,
  Install Tools, Publish Tools, Keyboard Shortcuts, About
- F1 shortcut opens Getting Started guide
- Add tooltips to Tool Builder section headings (Arguments, Steps,
  Output Template) that appear on hover over the label text
- Add tooltips to Registry page controls (search, filters, pagination)
- Enhance sidebar tooltips with keyboard shortcuts

Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
rob
2026-01-17 06:27:23 -04:00
co-authored by Claude Opus 4.5
parent 3f259449d4
commit 9baddeef18
6 changed files with 540 additions and 16 deletions
+11
View File
@@ -129,6 +129,7 @@ class RegistryPage(QWidget):
# Browse all button
self.btn_browse = QPushButton("Browse All")
self.btn_browse.setToolTip("Clear filters and show all available tools")
self.btn_browse.clicked.connect(self._browse_all)
header.addWidget(self.btn_browse)
@@ -143,6 +144,7 @@ class RegistryPage(QWidget):
# Search input
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Search tools...")
self.search_input.setToolTip("Search by name, description, or keywords (press Enter)")
self.search_input.returnPressed.connect(self._do_search)
filters_layout.addWidget(self.search_input, 2)
@@ -152,6 +154,7 @@ class RegistryPage(QWidget):
self.category_combo = QComboBox()
self.category_combo.addItems(["All", "Text", "Developer", "Data", "Other"])
self.category_combo.setMinimumWidth(100)
self.category_combo.setToolTip("Filter by tool category")
self.category_combo.currentTextChanged.connect(self._on_filter_changed)
filters_layout.addWidget(self.category_combo)
@@ -163,11 +166,13 @@ class RegistryPage(QWidget):
self.sort_combo.addItem("Newest", "published_at")
self.sort_combo.addItem("Name (A-Z)", "name")
self.sort_combo.setMinimumWidth(120)
self.sort_combo.setToolTip("Change sort order of results")
self.sort_combo.currentIndexChanged.connect(self._on_filter_changed)
filters_layout.addWidget(self.sort_combo)
# Search button
self.btn_search = QPushButton("Search")
self.btn_search.setToolTip("Search with current filters")
self.btn_search.clicked.connect(self._do_search)
filters_layout.addWidget(self.btn_search)
@@ -196,6 +201,7 @@ class RegistryPage(QWidget):
self.results_table = QTableWidget()
self.results_table.setColumnCount(6)
self.results_table.setHorizontalHeaderLabels(["", "Name", "Owner", "Rating", "Downloads", "Version"])
self.results_table.setToolTip("Click a tool to see details. ✓ = installed, ↑ = update available")
self.results_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed)
self.results_table.setColumnWidth(0, 30) # Installed indicator
self.results_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
@@ -215,6 +221,7 @@ class RegistryPage(QWidget):
pag_layout.setContentsMargins(0, 8, 0, 0)
self.btn_prev = QPushButton("← Previous")
self.btn_prev.setToolTip("Go to previous page of results")
self.btn_prev.clicked.connect(self._prev_page)
self.btn_prev.setEnabled(False)
pag_layout.addWidget(self.btn_prev)
@@ -228,6 +235,7 @@ class RegistryPage(QWidget):
pag_layout.addStretch()
self.btn_next = QPushButton("Next →")
self.btn_next.setToolTip("Go to next page of results")
self.btn_next.clicked.connect(self._next_page)
self.btn_next.setEnabled(False)
pag_layout.addWidget(self.btn_next)
@@ -263,6 +271,7 @@ class RegistryPage(QWidget):
self.version_combo.setMinimumWidth(120)
self.version_combo.addItem("Latest", None)
self.version_combo.setEnabled(False)
self.version_combo.setToolTip("Select which version to install")
version_row.addWidget(self.version_combo)
version_row.addStretch()
@@ -272,11 +281,13 @@ class RegistryPage(QWidget):
actions = QHBoxLayout()
self.btn_install = QPushButton("Install")
self.btn_install.setToolTip("Download and install this tool locally")
self.btn_install.clicked.connect(self._install_tool)
self.btn_install.setEnabled(False)
actions.addWidget(self.btn_install)
self.btn_update = QPushButton("Update Available")
self.btn_update.setToolTip("Update to the latest version from the registry")
self.btn_update.clicked.connect(self._install_tool)
self.btn_update.setEnabled(False)
self.btn_update.setStyleSheet("background-color: #48bb78; color: white;")
+56 -9
View File
@@ -51,10 +51,12 @@ class ToolBuilderPage(QWidget):
self.btn_cancel = QPushButton("Cancel")
self.btn_cancel.setObjectName("secondary")
self.btn_cancel.setToolTip("Cancel and return to tools page (Escape)")
self.btn_cancel.clicked.connect(self._cancel)
header_layout.addWidget(self.btn_cancel)
self.btn_save = QPushButton("Save")
self.btn_save.setToolTip("Save this tool (Ctrl+S)")
self.btn_save.clicked.connect(self._save)
header_layout.addWidget(self.btn_save)
@@ -76,14 +78,17 @@ class ToolBuilderPage(QWidget):
self.name_input = QLineEdit()
self.name_input.setPlaceholderText("my-tool")
self.name_input.setToolTip("Unique tool name using lowercase letters, numbers, and hyphens")
info_layout.addRow("Name:", self.name_input)
self.desc_input = QLineEdit()
self.desc_input.setPlaceholderText("A brief description of what this tool does")
self.desc_input.setToolTip("Brief description shown in tool listings and help text")
info_layout.addRow("Description:", self.desc_input)
self.category_combo = QComboBox()
self.category_combo.setEditable(True)
self.category_combo.setToolTip("Category for organizing tools (select or type custom)")
for cat in DEFAULT_CATEGORIES:
self.category_combo.addItem(cat)
info_layout.addRow("Category:", self.category_combo)
@@ -91,9 +96,19 @@ class ToolBuilderPage(QWidget):
left_layout.addWidget(info_box)
# Arguments group
args_box = QGroupBox("Arguments")
args_box = QGroupBox()
args_layout = QVBoxLayout(args_box)
args_label = QLabel("Arguments")
args_label.setObjectName("sectionHeading")
args_label.setToolTip(
"<p>Arguments are command-line flags users can pass to your tool.</p>"
"<p>Example: Adding --max with variable 'max' lets users run:<br>"
"<code>my-tool --max 100 &lt; input.txt</code></p>"
"<p>Use {max} in your prompts to reference the value.</p>"
)
args_layout.addWidget(args_label)
self.args_list = QListWidget()
self.args_list.itemDoubleClicked.connect(self._edit_argument)
args_layout.addWidget(self.args_list)
@@ -127,21 +142,37 @@ class ToolBuilderPage(QWidget):
right_layout.setSpacing(16)
# Steps group with view toggle
steps_box = QGroupBox("Steps")
steps_box = QGroupBox()
steps_layout = QVBoxLayout(steps_box)
# View toggle header
view_header = QHBoxLayout()
# Steps header with label and view toggle
steps_header = QHBoxLayout()
steps_label = QLabel("Steps")
steps_label.setObjectName("sectionHeading")
steps_label.setToolTip(
"<p>Steps define what your tool does, executed in order.</p>"
"<p><b>Step types:</b><br>"
"- Prompt: Call an AI provider with a template<br>"
"- Code: Run Python code to process data<br>"
"- Tool: Call another CmdForge tool</p>"
"<p>Use {variable} syntax to pass data between steps.<br>"
"Built-in: {input} contains stdin content.</p>"
)
steps_header.addWidget(steps_label)
steps_header.addStretch()
# View toggle buttons
self.btn_list_view = QPushButton("List")
self.btn_list_view.setCheckable(True)
self.btn_list_view.setChecked(True)
self.btn_list_view.setObjectName("viewToggle")
self.btn_list_view.setToolTip("View steps as a list (drag to reorder)")
self.btn_list_view.clicked.connect(lambda: self._set_view_mode(0))
self.btn_flow_view = QPushButton("Flow")
self.btn_flow_view.setCheckable(True)
self.btn_flow_view.setObjectName("viewToggle")
self.btn_flow_view.setToolTip("View steps as a visual flow graph")
self.btn_flow_view.clicked.connect(lambda: self._set_view_mode(1))
# Button group for mutual exclusivity
@@ -149,10 +180,9 @@ class ToolBuilderPage(QWidget):
self.view_group.addButton(self.btn_list_view, 0)
self.view_group.addButton(self.btn_flow_view, 1)
view_header.addWidget(self.btn_list_view)
view_header.addWidget(self.btn_flow_view)
view_header.addStretch()
steps_layout.addLayout(view_header)
steps_header.addWidget(self.btn_list_view)
steps_header.addWidget(self.btn_flow_view)
steps_layout.addLayout(steps_header)
# Stacked widget for list/flow views
self.steps_stack = QStackedWidget()
@@ -183,24 +213,29 @@ class ToolBuilderPage(QWidget):
# Step action buttons
steps_btns = QHBoxLayout()
self.btn_add_prompt = QPushButton("Add Prompt")
self.btn_add_prompt.setToolTip("Add an AI prompt step - calls your provider with a template")
self.btn_add_prompt.clicked.connect(self._add_prompt_step)
steps_btns.addWidget(self.btn_add_prompt)
self.btn_add_code = QPushButton("Add Code")
self.btn_add_code.setToolTip("Add a Python code step - process or transform data")
self.btn_add_code.clicked.connect(self._add_code_step)
steps_btns.addWidget(self.btn_add_code)
self.btn_add_tool = QPushButton("Add Tool")
self.btn_add_tool.setToolTip("Add a tool step - chain another CmdForge tool")
self.btn_add_tool.clicked.connect(self._add_tool_step)
steps_btns.addWidget(self.btn_add_tool)
self.btn_edit_step = QPushButton("Edit")
self.btn_edit_step.setObjectName("secondary")
self.btn_edit_step.setToolTip("Edit the selected step (or double-click)")
self.btn_edit_step.clicked.connect(self._edit_step)
steps_btns.addWidget(self.btn_edit_step)
self.btn_del_step = QPushButton("Delete")
self.btn_del_step.setObjectName("danger")
self.btn_del_step.setToolTip("Delete the selected step")
self.btn_del_step.clicked.connect(self._delete_step)
steps_btns.addWidget(self.btn_del_step)
@@ -210,9 +245,21 @@ class ToolBuilderPage(QWidget):
right_layout.addWidget(steps_box, 1) # Steps box gets stretch priority
# Output group
output_box = QGroupBox("Output Template")
output_box = QGroupBox()
output_layout = QVBoxLayout(output_box)
output_label = QLabel("Output Template")
output_label.setObjectName("sectionHeading")
output_label.setToolTip(
"<p>The output template defines what your tool prints.</p>"
"<p><b>Use {variable} to include step outputs:</b><br>"
"- {response} - typical AI response variable<br>"
"- {result} - common code step output<br>"
"- Any output_var you defined in steps</p>"
"<p>Example: <code>{response}</code> prints just the AI response.</p>"
)
output_layout.addWidget(output_label)
self.output_input = QTextEdit()
self.output_input.setPlaceholderText("Use {variable} to reference step outputs, e.g. {response}")
self.output_input.setPlainText("{response}") # Default value