Add step type icons and inline name editing

- Add icons module with programmatically drawn icons:
  - Speech bubble for prompt steps
  - Code brackets </> for code steps
  - Arrow icons for input/output nodes
- Display icons in list view next to step names
- Display icons in flow view node headers
- Add inline name editing in flow view via property_changed signal
- Sync name changes from flow view to tool model and list view

Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
rob
2026-01-16 01:21:20 -04:00
co-authored by Claude Opus 4.5
parent bc970fb9f7
commit 8e6b03cade
3 changed files with 378 additions and 53 deletions
+74 -3
View File
@@ -13,6 +13,7 @@ from ...tool import (
Tool, ToolArgument, PromptStep, CodeStep,
load_tool, save_tool, validate_tool_name, DEFAULT_CATEGORIES
)
from ..widgets.icons import get_prompt_icon, get_code_icon
class ToolBuilderPage(QWidget):
@@ -242,6 +243,7 @@ class ToolBuilderPage(QWidget):
self._flow_widget.node_double_clicked.connect(self._on_flow_node_double_clicked)
self._flow_widget.steps_deleted.connect(self._on_flow_steps_deleted)
self._flow_widget.steps_reordered.connect(self._on_flow_steps_reordered)
self._flow_widget.step_name_changed.connect(self._on_flow_step_name_changed)
# Replace placeholder
old_widget = self.steps_stack.widget(1)
@@ -283,6 +285,17 @@ class ToolBuilderPage(QWidget):
# Refresh the list view (flow view will be refreshed by set_tool)
self._refresh_steps()
def _on_flow_step_name_changed(self, step_index: int, new_name: str):
"""Handle step name change from flow view inline editing."""
if not self._tool or step_index < 0 or step_index >= len(self._tool.steps):
return
step = self._tool.steps[step_index]
step.name = new_name
# Refresh list view to show updated name
self._refresh_steps_list_only()
def _on_flow_steps_reordered(self, new_order: list):
"""Handle step reordering from flow view.
@@ -371,15 +384,20 @@ class ToolBuilderPage(QWidget):
prompt_count += 1
# Use custom name if set, otherwise default per-type naming
step_name = step.name if step.name else f"Prompt {prompt_count}"
text = f"{i}. {step_name} [{step.provider}] → ${step.output_var}"
text = f"{step_name} [{step.provider}] → ${step.output_var}"
icon = get_prompt_icon(20)
elif isinstance(step, CodeStep):
code_count += 1
step_name = step.name if step.name else f"Code {code_count}"
text = f"{i}. {step_name} [python] → ${step.output_var}"
text = f"{step_name} [python] → ${step.output_var}"
icon = get_code_icon(20)
else:
text = f"{i}. Unknown step"
text = f"Unknown step"
icon = None
item = QListWidgetItem(text)
if icon:
item.setIcon(icon)
item.setData(Qt.UserRole, step)
# Check for broken dependencies
@@ -401,6 +419,59 @@ class ToolBuilderPage(QWidget):
if self._flow_widget:
self._flow_widget.set_tool(self._tool)
def _refresh_steps_list_only(self):
"""Refresh only the steps list (not flow view).
Used when a change originated from the flow view to avoid
rebuilding the graph unnecessarily.
"""
self.steps_list.clear()
if self._tool and self._tool.steps:
# Count steps by type for default naming
prompt_count = 0
code_count = 0
# Track available variables for dependency checking
available_vars = {"input"}
if self._tool.arguments:
for arg in self._tool.arguments:
if arg.variable:
available_vars.add(arg.variable)
for i, step in enumerate(self._tool.steps, 1):
if isinstance(step, PromptStep):
prompt_count += 1
step_name = step.name if step.name else f"Prompt {prompt_count}"
text = f"{step_name} [{step.provider}] → ${step.output_var}"
icon = get_prompt_icon(20)
elif isinstance(step, CodeStep):
code_count += 1
step_name = step.name if step.name else f"Code {code_count}"
text = f"{step_name} [python] → ${step.output_var}"
icon = get_code_icon(20)
else:
text = f"Unknown step"
icon = None
item = QListWidgetItem(text)
if icon:
item.setIcon(icon)
item.setData(Qt.UserRole, step)
# Check for broken dependencies
refs = self._get_step_variable_refs(step)
broken_refs = [r for r in refs if r not in available_vars]
if broken_refs:
item.setForeground(Qt.red)
item.setToolTip(f"Missing variables: {', '.join(broken_refs)}")
self.steps_list.addItem(item)
# Add this step's output to available vars for next iteration
if hasattr(step, 'output_var') and step.output_var:
for var in step.output_var.split(','):
available_vars.add(var.strip())
def _add_argument(self):
"""Add a new argument."""
from ..dialogs.argument_dialog import ArgumentDialog