Improve flow view UX and fix step naming
- Remove Fit All button, F key shortcut is sufficient - Add right-click context menu with "Fit All (F)" option - Add floating help banner that appears on focus and auto-hides - Banner stays visible when mouse hovers over it - Fix default step naming to increment per type (Code 1, Prompt 1, Code 2) instead of globally (Code 1, Prompt 2, Code 3) - List view still shows overall step number (1, 2, 3) with per-type names Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4d9a0e5943
commit
fb879d09ea
|
|
@ -307,13 +307,19 @@ class ToolBuilderPage(QWidget):
|
|||
"""Refresh steps list and flow view."""
|
||||
self.steps_list.clear()
|
||||
if self._tool and self._tool.steps:
|
||||
# Count steps by type for default naming
|
||||
prompt_count = 0
|
||||
code_count = 0
|
||||
|
||||
for i, step in enumerate(self._tool.steps, 1):
|
||||
if isinstance(step, PromptStep):
|
||||
# Use custom name if set, otherwise default format
|
||||
step_name = step.name if step.name else f"Prompt {i}"
|
||||
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}"
|
||||
elif isinstance(step, CodeStep):
|
||||
step_name = step.name if step.name else f"Code {i}"
|
||||
code_count += 1
|
||||
step_name = step.name if step.name else f"Code {code_count}"
|
||||
text = f"{i}. {step_name} [python] → ${step.output_var}"
|
||||
else:
|
||||
text = f"{i}. Unknown step"
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
from typing import Optional, List
|
||||
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QPushButton
|
||||
from PySide6.QtCore import Signal, Qt, QTimer, QEvent
|
||||
from PySide6.QtGui import QKeyEvent
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QMenu
|
||||
from PySide6.QtCore import Signal, Qt, QTimer, QEvent, QPropertyAnimation, QEasingCurve
|
||||
from PySide6.QtGui import QKeyEvent, QAction
|
||||
|
||||
from NodeGraphQt import NodeGraph, BaseNode
|
||||
|
||||
|
|
@ -138,52 +138,6 @@ class FlowGraphWidget(QWidget):
|
|||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
# Navigation help bar
|
||||
help_bar = QWidget()
|
||||
help_bar.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: #edf2f7;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
QLabel {
|
||||
color: #718096;
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
""")
|
||||
help_layout = QHBoxLayout(help_bar)
|
||||
help_layout.setContentsMargins(8, 2, 8, 2)
|
||||
|
||||
help_text = QLabel(
|
||||
"Pan: Middle-click drag | "
|
||||
"Zoom: Scroll wheel | "
|
||||
"Select: Click or drag box | "
|
||||
"Edit step: Double-click node"
|
||||
)
|
||||
help_layout.addWidget(help_text)
|
||||
help_layout.addStretch()
|
||||
|
||||
# Fit view button
|
||||
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)
|
||||
|
||||
# Create node graph
|
||||
self._graph = NodeGraph()
|
||||
|
||||
|
|
@ -199,9 +153,49 @@ class FlowGraphWidget(QWidget):
|
|||
# Add graph widget
|
||||
layout.addWidget(self._graph.widget, 1)
|
||||
|
||||
# Install event filter to catch F key on the graph widget
|
||||
# Install event filter to catch F key and focus events on the graph widget
|
||||
self._graph.widget.installEventFilter(self)
|
||||
|
||||
# Create floating help banner (overlay on graph widget)
|
||||
self._help_banner = QLabel(self._graph.widget)
|
||||
self._help_banner.setText(
|
||||
"Pan: Middle-click drag | "
|
||||
"Zoom: Scroll wheel | "
|
||||
"Select: Click or drag box | "
|
||||
"Edit: Double-click | "
|
||||
"Fit All: F"
|
||||
)
|
||||
self._help_banner.setStyleSheet("""
|
||||
QLabel {
|
||||
background-color: rgba(45, 55, 72, 0.9);
|
||||
color: #e2e8f0;
|
||||
font-size: 11px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
self._help_banner.adjustSize()
|
||||
self._help_banner.hide()
|
||||
|
||||
# Animation for fading
|
||||
self._fade_animation = QPropertyAnimation(self._help_banner, b"windowOpacity")
|
||||
self._fade_animation.setDuration(500)
|
||||
self._fade_animation.setEasingCurve(QEasingCurve.InOutQuad)
|
||||
|
||||
# Timer for auto-hide
|
||||
self._hide_timer = QTimer(self)
|
||||
self._hide_timer.setSingleShot(True)
|
||||
self._hide_timer.timeout.connect(self._fade_out_banner)
|
||||
|
||||
# Track if mouse is over banner
|
||||
self._help_banner.setMouseTracking(True)
|
||||
self._help_banner.enterEvent = self._on_banner_enter
|
||||
self._help_banner.leaveEvent = self._on_banner_leave
|
||||
|
||||
# Set up context menu
|
||||
self._graph.widget.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self._graph.widget.customContextMenuRequested.connect(self._show_context_menu)
|
||||
|
||||
def set_tool(self, tool: Optional[Tool]):
|
||||
"""Set the tool to visualize."""
|
||||
self._tool = tool
|
||||
|
|
@ -234,10 +228,15 @@ class FlowGraphWidget(QWidget):
|
|||
x_pos = 0
|
||||
prev_node = self._input_node
|
||||
|
||||
# Count steps by type for default naming
|
||||
prompt_count = 0
|
||||
code_count = 0
|
||||
|
||||
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}'
|
||||
prompt_count += 1
|
||||
# Use custom name if set, otherwise default to "Prompt N" (per type)
|
||||
node_name = step.name if step.name else f'Prompt {prompt_count}'
|
||||
node = self._graph.create_node(
|
||||
'cmdforge.PromptNode',
|
||||
name=node_name,
|
||||
|
|
@ -245,8 +244,9 @@ class FlowGraphWidget(QWidget):
|
|||
)
|
||||
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}'
|
||||
code_count += 1
|
||||
# Use custom name if set, otherwise default to "Code N" (per type)
|
||||
node_name = step.name if step.name else f'Code {code_count}'
|
||||
node = self._graph.create_node(
|
||||
'cmdforge.CodeNode',
|
||||
name=node_name,
|
||||
|
|
@ -318,12 +318,53 @@ class FlowGraphWidget(QWidget):
|
|||
super().keyPressEvent(event)
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Filter events from the graph widget to catch F key."""
|
||||
"""Filter events from the graph widget to catch F key and focus."""
|
||||
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_F:
|
||||
self.fit_all_nodes()
|
||||
return True # Event handled
|
||||
elif event.type() == QEvent.FocusIn:
|
||||
self._show_help_banner()
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
def _show_help_banner(self):
|
||||
"""Show the help banner with fade effect."""
|
||||
if not self._help_banner:
|
||||
return
|
||||
|
||||
# Position at top center of the graph widget
|
||||
parent_width = self._graph.widget.width()
|
||||
banner_width = self._help_banner.width()
|
||||
x = (parent_width - banner_width) // 2
|
||||
self._help_banner.move(x, 10)
|
||||
|
||||
# Show and start auto-hide timer
|
||||
self._help_banner.show()
|
||||
self._hide_timer.start(3000) # Hide after 3 seconds
|
||||
|
||||
def _fade_out_banner(self):
|
||||
"""Fade out the help banner."""
|
||||
if self._help_banner and self._help_banner.isVisible():
|
||||
# Use a simple hide with timer since windowOpacity doesn't work well on child widgets
|
||||
self._help_banner.hide()
|
||||
|
||||
def _on_banner_enter(self, event):
|
||||
"""Mouse entered banner - stop hide timer."""
|
||||
self._hide_timer.stop()
|
||||
|
||||
def _on_banner_leave(self, event):
|
||||
"""Mouse left banner - restart hide timer."""
|
||||
self._hide_timer.start(1500) # Hide 1.5 seconds after mouse leaves
|
||||
|
||||
def _show_context_menu(self, pos):
|
||||
"""Show context menu."""
|
||||
menu = QMenu(self._graph.widget)
|
||||
|
||||
fit_action = QAction("Fit All (F)", menu)
|
||||
fit_action.triggered.connect(self.fit_all_nodes)
|
||||
menu.addAction(fit_action)
|
||||
|
||||
menu.exec_(self._graph.widget.mapToGlobal(pos))
|
||||
|
||||
def _on_node_double_clicked(self, node):
|
||||
"""Handle node double-click."""
|
||||
if hasattr(node, '_step_index') and node._step_index >= 0:
|
||||
|
|
|
|||
Loading…
Reference in New Issue