Add provider fallback, visual schema builder, and UI improvements

Provider fallback system:
- Add optional fallback field to Provider dataclass
- Automatically try fallback provider when primary fails (timeout, error, offline)
- Prevent infinite loops with circular fallback detection
- Add fallback column and dropdown in providers UI

Visual schema builder for prompt steps:
- Add SchemaBuilderDialog with table-based field editor
- Support field name, type, description, and required flag
- Live JSON schema preview
- Integrate into PromptStepDialog with Edit Schema button
- Fix output_schema being lost when editing prompt steps

Other improvements:
- Add search box to tools page
- Improve schema example generation for structured output
- Update CLAUDE.md documentation

Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
rob
2026-02-21 00:50:18 -04:00
co-authored by Claude Opus 4.5
parent 8581775002
commit 71c6358b9d
9 changed files with 892 additions and 99 deletions
+46 -1
View File
@@ -9,7 +9,7 @@ import yaml
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QSplitter,
QTreeWidget, QTreeWidgetItem, QTextEdit, QLabel,
QPushButton, QGroupBox, QMessageBox, QFrame
QPushButton, QGroupBox, QMessageBox, QFrame, QLineEdit
)
from PySide6.QtCore import Qt, QThread, Signal, QTimer
from PySide6.QtGui import QFont, QColor, QBrush, QShortcut, QKeySequence
@@ -402,6 +402,13 @@ class ToolsPage(QWidget):
left_layout.setContentsMargins(0, 0, 0, 0)
left_layout.setSpacing(8)
# Search box
self.search_box = QLineEdit()
self.search_box.setPlaceholderText("Search tools...")
self.search_box.setClearButtonEnabled(True)
self.search_box.textChanged.connect(self._filter_tools)
left_layout.addWidget(self.search_box)
self.tool_tree = QTreeWidget()
self.tool_tree.setHeaderHidden(True)
self.tool_tree.setIndentation(16)
@@ -555,6 +562,7 @@ class ToolsPage(QWidget):
def refresh(self):
"""Refresh the tool list."""
self.search_box.clear()
self.tool_tree.clear()
self._current_tool = None
self.info_text.clear()
@@ -741,6 +749,43 @@ class ToolsPage(QWidget):
self.refresh()
self.main_window.show_status(f"Status updated for '{tool_name}'")
def _filter_tools(self, text: str):
"""Filter tools by name or description based on search text."""
search = text.lower().strip()
for i in range(self.tool_tree.topLevelItemCount()):
category_item = self.tool_tree.topLevelItem(i)
visible_children = 0
for j in range(category_item.childCount()):
tool_item = category_item.child(j)
tool_name = tool_item.data(0, Qt.UserRole)
if not search:
# No filter - show all
tool_item.setHidden(False)
visible_children += 1
else:
# Check if name matches
matches = search in tool_name.lower()
# Also check description if we have the tool loaded
if not matches:
tool = load_tool(tool_name)
if tool and tool.description:
matches = search in tool.description.lower()
tool_item.setHidden(not matches)
if matches:
visible_children += 1
# Hide category if no children match
category_item.setHidden(visible_children == 0)
# Expand categories when filtering to show results
if search and visible_children > 0:
category_item.setExpanded(True)
def _on_selection_changed(self):
"""Handle tool selection change."""
items = self.tool_tree.selectedItems()