Add custom step names and fix F key shortcut

- Add name field to PromptStep, CodeStep, and ToolStep dataclasses
- Update step dialogs to allow editing step names
- Display custom step names in both list and flow views
- Fix F key shortcut by installing event filter on graph widget
- Steps without custom names show default "Prompt N" / "Code N"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-01-15 22:16:15 -04:00
parent 3c39cc9cda
commit 88c9a8a1e7
4 changed files with 106 additions and 15 deletions

View File

@ -36,6 +36,11 @@ class PromptStepDialog(QDialog):
form = QFormLayout()
form.setSpacing(12)
# Step name (optional)
self.name_input = QLineEdit()
self.name_input.setPlaceholderText("Optional display name")
form.addRow("Step name:", self.name_input)
# Provider selection
self.provider_combo = QComboBox()
providers = load_providers()
@ -95,6 +100,10 @@ class PromptStepDialog(QDialog):
def _load_step(self, step: PromptStep):
"""Load step data into form."""
# Load name
if step.name:
self.name_input.setText(step.name)
idx = self.provider_combo.findText(step.provider)
if idx >= 0:
self.provider_combo.setCurrentIndex(idx)
@ -130,11 +139,14 @@ class PromptStepDialog(QDialog):
# Don't store "None" profile
if profile == "None":
profile = None
# Get name, use None if empty
name = self.name_input.text().strip() or None
return PromptStep(
prompt=self.prompt_input.toPlainText(),
provider=self.provider_combo.currentText(),
output_var=self.output_input.text().strip(),
profile=profile
profile=profile,
name=name
)
@ -179,9 +191,15 @@ class CodeStepDialog(QDialog):
layout = QVBoxLayout(self)
layout.setSpacing(12)
# Top: Output variable
# Top: Step name and Output variable
form = QFormLayout()
form.setSpacing(8)
# Step name (optional)
self.name_input = QLineEdit()
self.name_input.setPlaceholderText("Optional display name")
form.addRow("Step name:", self.name_input)
self.output_input = QLineEdit()
self.output_input.setPlaceholderText("result")
self.output_input.setText("result")
@ -365,6 +383,9 @@ IMPORTANT: Return ONLY executable inline Python code. No function definitions, n
def _load_step(self, step: CodeStep):
"""Load step data into form."""
# Load name
if step.name:
self.name_input.setText(step.name)
self.output_input.setText(step.output_var)
self.code_input.setPlainText(step.code)
@ -396,7 +417,10 @@ IMPORTANT: Return ONLY executable inline Python code. No function definitions, n
def get_step(self) -> CodeStep:
"""Get the step from form data."""
# Get name, use None if empty
name = self.name_input.text().strip() or None
return CodeStep(
code=self.code_input.toPlainText(),
output_var=self.output_input.text().strip()
output_var=self.output_input.text().strip(),
name=name
)

View File

@ -309,9 +309,12 @@ class ToolBuilderPage(QWidget):
if self._tool and self._tool.steps:
for i, step in enumerate(self._tool.steps, 1):
if isinstance(step, PromptStep):
text = f"{i}. Prompt [{step.provider}] → ${step.output_var}"
# Use custom name if set, otherwise default format
step_name = step.name if step.name else f"Prompt {i}"
text = f"{i}. {step_name} [{step.provider}] → ${step.output_var}"
elif isinstance(step, CodeStep):
text = f"{i}. Code [python] → ${step.output_var}"
step_name = step.name if step.name else f"Code {i}"
text = f"{i}. {step_name} [python] → ${step.output_var}"
else:
text = f"{i}. Unknown step"
item = QListWidgetItem(text)

View File

@ -2,8 +2,9 @@
from typing import Optional, List
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout
from PySide6.QtCore import Signal, Qt, QTimer
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QPushButton
from PySide6.QtCore import Signal, Qt, QTimer, QEvent
from PySide6.QtGui import QKeyEvent
from NodeGraphQt import NodeGraph, BaseNode
@ -163,9 +164,23 @@ class FlowGraphWidget(QWidget):
help_layout.addStretch()
# Fit view button
fit_label = QLabel("Press F to fit all")
fit_label.setStyleSheet("color: #667eea; font-weight: 500;")
help_layout.addWidget(fit_label)
self.btn_fit = QPushButton("Fit All (F)")
self.btn_fit.setStyleSheet("""
QPushButton {
background-color: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 4px 12px;
font-size: 11px;
font-weight: 500;
}
QPushButton:hover {
background-color: #5a67d8;
}
""")
self.btn_fit.clicked.connect(self.fit_all_nodes)
help_layout.addWidget(self.btn_fit)
layout.addWidget(help_bar)
@ -184,6 +199,9 @@ class FlowGraphWidget(QWidget):
# Add graph widget
layout.addWidget(self._graph.widget, 1)
# Install event filter to catch F key on the graph widget
self._graph.widget.installEventFilter(self)
def set_tool(self, tool: Optional[Tool]):
"""Set the tool to visualize."""
self._tool = tool
@ -218,16 +236,20 @@ class FlowGraphWidget(QWidget):
for i, step in enumerate(self._tool.steps or []):
if isinstance(step, PromptStep):
# Use custom name if set, otherwise default to "Prompt N"
node_name = step.name if step.name else f'Prompt {i+1}'
node = self._graph.create_node(
'cmdforge.PromptNode',
name=f'Prompt {i+1}',
name=node_name,
pos=[x_pos, 0]
)
node.set_step(step, i)
elif isinstance(step, CodeStep):
# Use custom name if set, otherwise default to "Code N"
node_name = step.name if step.name else f'Code {i+1}'
node = self._graph.create_node(
'cmdforge.CodeNode',
name=f'Code {i+1}',
name=node_name,
pos=[x_pos, 0]
)
node.set_step(step, i)
@ -272,6 +294,36 @@ class FlowGraphWidget(QWidget):
# Clear selection
self._graph.clear_selection()
def fit_all_nodes(self):
"""Fit view to show all nodes."""
if not self._graph:
return
all_nodes = self._graph.all_nodes()
if not all_nodes:
return
# Select all, fit, then clear selection
for node in all_nodes:
node.set_selected(True)
self._graph.fit_to_selection()
self._graph.clear_selection()
def keyPressEvent(self, event: QKeyEvent):
"""Handle keyboard shortcuts."""
if event.key() == Qt.Key_F:
self.fit_all_nodes()
event.accept()
else:
super().keyPressEvent(event)
def eventFilter(self, obj, event):
"""Filter events from the graph widget to catch F key."""
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_F:
self.fit_all_nodes()
return True # Event handled
return super().eventFilter(obj, event)
def _on_node_double_clicked(self, node):
"""Handle node double-click."""
if hasattr(node, '_step_index') and node._step_index >= 0:

View File

@ -50,6 +50,7 @@ class PromptStep:
output_var: str # Variable to store output
prompt_file: Optional[str] = None # Optional filename for external prompt
profile: Optional[str] = None # Optional AI persona profile name
name: Optional[str] = None # Optional display name for the step
def to_dict(self) -> dict:
d = {
@ -62,6 +63,8 @@ class PromptStep:
d["prompt_file"] = self.prompt_file
if self.profile:
d["profile"] = self.profile
if self.name:
d["name"] = self.name
return d
@classmethod
@ -71,7 +74,8 @@ class PromptStep:
provider=data["provider"],
output_var=data["output_var"],
prompt_file=data.get("prompt_file"),
profile=data.get("profile")
profile=data.get("profile"),
name=data.get("name")
)
@ -81,6 +85,7 @@ class CodeStep:
code: str # Python code (inline or loaded from file)
output_var: str # Variable name(s) to capture (comma-separated for multiple)
code_file: Optional[str] = None # Optional filename for external code
name: Optional[str] = None # Optional display name for the step
def to_dict(self) -> dict:
d = {
@ -90,6 +95,8 @@ class CodeStep:
}
if self.code_file:
d["code_file"] = self.code_file
if self.name:
d["name"] = self.name
return d
@classmethod
@ -97,7 +104,8 @@ class CodeStep:
return cls(
code=data.get("code", ""),
output_var=data["output_var"],
code_file=data.get("code_file")
code_file=data.get("code_file"),
name=data.get("name")
)
@ -109,6 +117,7 @@ class ToolStep:
input_template: str = "{input}" # Input template (supports variable substitution)
args: dict = field(default_factory=dict) # Arguments to pass to the tool
provider: Optional[str] = None # Provider override for the called tool
name: Optional[str] = None # Optional display name for the step
def to_dict(self) -> dict:
d = {
@ -122,6 +131,8 @@ class ToolStep:
d["args"] = self.args
if self.provider:
d["provider"] = self.provider
if self.name:
d["name"] = self.name
return d
@classmethod
@ -131,7 +142,8 @@ class ToolStep:
output_var=data["output_var"],
input_template=data.get("input", "{input}"),
args=data.get("args", {}),
provider=data.get("provider")
provider=data.get("provider"),
name=data.get("name")
)