Add tool marketplace UI enhancements
- Browse all tools on page load without search - Category filter dropdown (Text, Developer, Data, etc.) - Sort options (downloads, rating, newest, name) - Star ratings display in results table - Clickable tags for filtering - Installed indicator (✓) for local tools - Update available indicator (↑) for newer versions - Pagination controls for large result sets - Publisher reputation info in details Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4fe2d26244
commit
518a04a8b0
13
README.md
13
README.md
|
|
@ -431,9 +431,16 @@ The graphical interface provides a modern desktop experience:
|
|||
- Test tools before saving
|
||||
|
||||
### Registry Browser
|
||||
- Search community tools by name or keyword
|
||||
- View tool details, downloads, and ratings
|
||||
- One-click install to your local machine
|
||||
- **Browse all** tools on page load (no search required)
|
||||
- **Search** by name, description, or keyword
|
||||
- **Filter** by category (Text, Developer, Data, Other)
|
||||
- **Sort** by popularity, rating, newest, or name
|
||||
- **Star ratings** displayed in results and details
|
||||
- **Clickable tags** to filter by tag
|
||||
- **Installed indicator** (✓) shows which tools you have
|
||||
- **Update available** (↑) when newer version exists
|
||||
- **Pagination** for browsing large result sets
|
||||
- **Publisher info** showing reputation and total downloads
|
||||
|
||||
### Provider Management
|
||||
- Add and configure AI providers
|
||||
|
|
|
|||
|
|
@ -3,30 +3,45 @@
|
|||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLineEdit,
|
||||
QPushButton, QTableWidget, QTableWidgetItem, QLabel,
|
||||
QHeaderView, QGroupBox, QTextEdit, QSplitter, QMessageBox
|
||||
QHeaderView, QGroupBox, QTextEdit, QSplitter, QMessageBox,
|
||||
QComboBox, QFrame
|
||||
)
|
||||
from PySide6.QtCore import Qt, QThread, Signal
|
||||
from PySide6.QtGui import QColor
|
||||
|
||||
from ...registry_client import RegistryClient, RegistryError
|
||||
from ...config import load_config
|
||||
from ...tool import list_tools, load_tool
|
||||
|
||||
|
||||
class SearchWorker(QThread):
|
||||
"""Background worker for registry search."""
|
||||
finished = Signal(list)
|
||||
finished = Signal(object) # PaginatedResponse
|
||||
error = Signal(str)
|
||||
|
||||
def __init__(self, query: str, page: int = 1):
|
||||
def __init__(self, query: str = "", category: str = None, sort: str = "downloads",
|
||||
tags: list = None, page: int = 1, per_page: int = 20):
|
||||
super().__init__()
|
||||
self.query = query
|
||||
self.category = category
|
||||
self.sort = sort
|
||||
self.tags = tags
|
||||
self.page = page
|
||||
self.per_page = per_page
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
client = RegistryClient()
|
||||
result = client.search_tools(self.query, page=self.page, per_page=20)
|
||||
# result is a PaginatedResponse with data attribute
|
||||
self.finished.emit(result.data)
|
||||
result = client.search_tools(
|
||||
self.query,
|
||||
category=self.category if self.category and self.category != "All" else None,
|
||||
tags=self.tags,
|
||||
page=self.page,
|
||||
per_page=self.per_page,
|
||||
sort=self.sort,
|
||||
include_facets=True
|
||||
)
|
||||
self.finished.emit(result)
|
||||
except Exception as e:
|
||||
self.error.emit(str(e))
|
||||
|
||||
|
|
@ -59,6 +74,10 @@ class RegistryPage(QWidget):
|
|||
self._search_worker = None
|
||||
self._install_worker = None
|
||||
self._selected_tool = None
|
||||
self._current_page = 1
|
||||
self._total_pages = 1
|
||||
self._current_tags = []
|
||||
self._installed_tools = {} # name -> version
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
|
|
@ -68,26 +87,70 @@ class RegistryPage(QWidget):
|
|||
layout.setSpacing(16)
|
||||
|
||||
# Header
|
||||
header = QHBoxLayout()
|
||||
title = QLabel("Tool Registry")
|
||||
title.setObjectName("heading")
|
||||
layout.addWidget(title)
|
||||
header.addWidget(title)
|
||||
header.addStretch()
|
||||
|
||||
# Search bar
|
||||
search_box = QWidget()
|
||||
search_layout = QHBoxLayout(search_box)
|
||||
search_layout.setContentsMargins(0, 0, 0, 0)
|
||||
search_layout.setSpacing(8)
|
||||
# Browse all button
|
||||
self.btn_browse = QPushButton("Browse All")
|
||||
self.btn_browse.clicked.connect(self._browse_all)
|
||||
header.addWidget(self.btn_browse)
|
||||
|
||||
layout.addLayout(header)
|
||||
|
||||
# Search and filters row
|
||||
filters_box = QWidget()
|
||||
filters_layout = QHBoxLayout(filters_box)
|
||||
filters_layout.setContentsMargins(0, 0, 0, 0)
|
||||
filters_layout.setSpacing(12)
|
||||
|
||||
# Search input
|
||||
self.search_input = QLineEdit()
|
||||
self.search_input.setPlaceholderText("Search tools...")
|
||||
self.search_input.returnPressed.connect(self._do_search)
|
||||
search_layout.addWidget(self.search_input, 1)
|
||||
filters_layout.addWidget(self.search_input, 2)
|
||||
|
||||
# Category filter
|
||||
cat_label = QLabel("Category:")
|
||||
filters_layout.addWidget(cat_label)
|
||||
self.category_combo = QComboBox()
|
||||
self.category_combo.addItems(["All", "Text", "Developer", "Data", "Other"])
|
||||
self.category_combo.setMinimumWidth(100)
|
||||
self.category_combo.currentTextChanged.connect(self._on_filter_changed)
|
||||
filters_layout.addWidget(self.category_combo)
|
||||
|
||||
# Sort dropdown
|
||||
sort_label = QLabel("Sort:")
|
||||
filters_layout.addWidget(sort_label)
|
||||
self.sort_combo = QComboBox()
|
||||
self.sort_combo.addItem("Most Popular", "downloads")
|
||||
self.sort_combo.addItem("Highest Rated", "rating")
|
||||
self.sort_combo.addItem("Newest", "newest")
|
||||
self.sort_combo.addItem("Name (A-Z)", "name")
|
||||
self.sort_combo.setMinimumWidth(120)
|
||||
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.clicked.connect(self._do_search)
|
||||
search_layout.addWidget(self.btn_search)
|
||||
filters_layout.addWidget(self.btn_search)
|
||||
|
||||
layout.addWidget(search_box)
|
||||
layout.addWidget(filters_box)
|
||||
|
||||
# Active tags display
|
||||
self.tags_widget = QWidget()
|
||||
self.tags_layout = QHBoxLayout(self.tags_widget)
|
||||
self.tags_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.tags_layout.setSpacing(8)
|
||||
self.tags_label = QLabel("Filtering by tags:")
|
||||
self.tags_label.setStyleSheet("color: #718096;")
|
||||
self.tags_layout.addWidget(self.tags_label)
|
||||
self.tags_layout.addStretch()
|
||||
self.tags_widget.hide()
|
||||
layout.addWidget(self.tags_widget)
|
||||
|
||||
# Content splitter
|
||||
splitter = QSplitter(Qt.Horizontal)
|
||||
|
|
@ -98,18 +161,46 @@ class RegistryPage(QWidget):
|
|||
left_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.results_table = QTableWidget()
|
||||
self.results_table.setColumnCount(4)
|
||||
self.results_table.setHorizontalHeaderLabels(["Name", "Owner", "Downloads", "Version"])
|
||||
self.results_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
self.results_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
||||
self.results_table.setColumnCount(6)
|
||||
self.results_table.setHorizontalHeaderLabels(["", "Name", "Owner", "Rating", "Downloads", "Version"])
|
||||
self.results_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed)
|
||||
self.results_table.setColumnWidth(0, 30) # Installed indicator
|
||||
self.results_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
||||
self.results_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
||||
self.results_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeToContents)
|
||||
self.results_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeToContents)
|
||||
self.results_table.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeToContents)
|
||||
self.results_table.setSelectionBehavior(QTableWidget.SelectRows)
|
||||
self.results_table.setSelectionMode(QTableWidget.SingleSelection)
|
||||
self.results_table.verticalHeader().setVisible(False)
|
||||
self.results_table.itemSelectionChanged.connect(self._on_selection_changed)
|
||||
left_layout.addWidget(self.results_table)
|
||||
|
||||
# Pagination controls
|
||||
pagination = QWidget()
|
||||
pag_layout = QHBoxLayout(pagination)
|
||||
pag_layout.setContentsMargins(0, 8, 0, 0)
|
||||
|
||||
self.btn_prev = QPushButton("← Previous")
|
||||
self.btn_prev.clicked.connect(self._prev_page)
|
||||
self.btn_prev.setEnabled(False)
|
||||
pag_layout.addWidget(self.btn_prev)
|
||||
|
||||
pag_layout.addStretch()
|
||||
|
||||
self.page_label = QLabel("Page 1 of 1")
|
||||
self.page_label.setStyleSheet("color: #718096;")
|
||||
pag_layout.addWidget(self.page_label)
|
||||
|
||||
pag_layout.addStretch()
|
||||
|
||||
self.btn_next = QPushButton("Next →")
|
||||
self.btn_next.clicked.connect(self._next_page)
|
||||
self.btn_next.setEnabled(False)
|
||||
pag_layout.addWidget(self.btn_next)
|
||||
|
||||
left_layout.addWidget(pagination)
|
||||
|
||||
splitter.addWidget(left)
|
||||
|
||||
# Right: Tool details
|
||||
|
|
@ -123,18 +214,31 @@ class RegistryPage(QWidget):
|
|||
self.details_text = QTextEdit()
|
||||
self.details_text.setReadOnly(True)
|
||||
self.details_text.setPlaceholderText("Select a tool to view details")
|
||||
self.details_text.anchorClicked = self._on_tag_clicked
|
||||
self.details_text.setOpenExternalLinks(False)
|
||||
details_layout.addWidget(self.details_text)
|
||||
|
||||
right_layout.addWidget(details_box, 1)
|
||||
|
||||
# Install button
|
||||
# Action buttons
|
||||
actions = QHBoxLayout()
|
||||
|
||||
self.btn_install = QPushButton("Install")
|
||||
self.btn_install.clicked.connect(self._install_tool)
|
||||
self.btn_install.setEnabled(False)
|
||||
right_layout.addWidget(self.btn_install)
|
||||
actions.addWidget(self.btn_install)
|
||||
|
||||
self.btn_update = QPushButton("Update Available")
|
||||
self.btn_update.clicked.connect(self._install_tool)
|
||||
self.btn_update.setEnabled(False)
|
||||
self.btn_update.setStyleSheet("background-color: #48bb78; color: white;")
|
||||
self.btn_update.hide()
|
||||
actions.addWidget(self.btn_update)
|
||||
|
||||
right_layout.addLayout(actions)
|
||||
|
||||
splitter.addWidget(right)
|
||||
splitter.setSizes([500, 500])
|
||||
splitter.setSizes([550, 450])
|
||||
|
||||
layout.addWidget(splitter, 1)
|
||||
|
||||
|
|
@ -144,43 +248,147 @@ class RegistryPage(QWidget):
|
|||
layout.addWidget(self.status_label)
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh on page enter."""
|
||||
pass # Could auto-search popular tools
|
||||
"""Refresh on page enter - load popular tools."""
|
||||
self._load_installed_tools()
|
||||
self._browse_all()
|
||||
|
||||
def _load_installed_tools(self):
|
||||
"""Load list of installed tools and their versions."""
|
||||
self._installed_tools = {}
|
||||
for name in list_tools():
|
||||
tool = load_tool(name)
|
||||
if tool:
|
||||
self._installed_tools[name] = tool.version or "1.0.0"
|
||||
|
||||
def _browse_all(self):
|
||||
"""Browse all tools (no search query)."""
|
||||
self.search_input.clear()
|
||||
self._current_page = 1
|
||||
self._do_search()
|
||||
|
||||
def _on_filter_changed(self):
|
||||
"""Handle filter/sort change."""
|
||||
self._current_page = 1
|
||||
self._do_search()
|
||||
|
||||
def _do_search(self):
|
||||
"""Perform search."""
|
||||
"""Perform search with current filters."""
|
||||
query = self.search_input.text().strip()
|
||||
if not query:
|
||||
return
|
||||
category = self.category_combo.currentText()
|
||||
sort = self.sort_combo.currentData()
|
||||
|
||||
self.btn_search.setEnabled(False)
|
||||
self.btn_browse.setEnabled(False)
|
||||
self.status_label.setText("Searching...")
|
||||
self.results_table.setRowCount(0)
|
||||
|
||||
self._search_worker = SearchWorker(query)
|
||||
self._search_worker = SearchWorker(
|
||||
query=query,
|
||||
category=category,
|
||||
sort=sort,
|
||||
tags=self._current_tags if self._current_tags else None,
|
||||
page=self._current_page,
|
||||
per_page=20
|
||||
)
|
||||
self._search_worker.finished.connect(self._on_search_complete)
|
||||
self._search_worker.error.connect(self._on_search_error)
|
||||
self._search_worker.start()
|
||||
|
||||
def _on_search_complete(self, tools: list):
|
||||
def _on_search_complete(self, result):
|
||||
"""Handle search results."""
|
||||
self.btn_search.setEnabled(True)
|
||||
self.status_label.setText(f"Found {len(tools)} tools")
|
||||
self.btn_browse.setEnabled(True)
|
||||
|
||||
tools = result.data
|
||||
self._total_pages = result.total_pages or 1
|
||||
total = result.total or len(tools)
|
||||
|
||||
self.status_label.setText(f"Found {total} tools")
|
||||
self.page_label.setText(f"Page {self._current_page} of {self._total_pages}")
|
||||
|
||||
# Update pagination buttons
|
||||
self.btn_prev.setEnabled(self._current_page > 1)
|
||||
self.btn_next.setEnabled(self._current_page < self._total_pages)
|
||||
|
||||
self.results_table.setRowCount(len(tools))
|
||||
for row, tool in enumerate(tools):
|
||||
name_item = QTableWidgetItem(tool.get("name", ""))
|
||||
tool_name = tool.get("name", "")
|
||||
|
||||
# Installed indicator
|
||||
installed_item = QTableWidgetItem()
|
||||
if tool_name in self._installed_tools:
|
||||
installed_version = self._installed_tools[tool_name]
|
||||
registry_version = tool.get("version", "1.0.0")
|
||||
if installed_version != registry_version:
|
||||
installed_item.setText("↑") # Update available
|
||||
installed_item.setToolTip(f"Update available: {installed_version} → {registry_version}")
|
||||
installed_item.setForeground(QColor("#48bb78"))
|
||||
else:
|
||||
installed_item.setText("✓")
|
||||
installed_item.setToolTip("Installed")
|
||||
installed_item.setForeground(QColor("#4299e1"))
|
||||
installed_item.setTextAlignment(Qt.AlignCenter)
|
||||
self.results_table.setItem(row, 0, installed_item)
|
||||
|
||||
# Name
|
||||
name_item = QTableWidgetItem(tool_name)
|
||||
name_item.setData(Qt.UserRole, tool)
|
||||
self.results_table.setItem(row, 0, name_item)
|
||||
self.results_table.setItem(row, 1, QTableWidgetItem(tool.get("owner", "")))
|
||||
self.results_table.setItem(row, 2, QTableWidgetItem(str(tool.get("downloads", 0))))
|
||||
self.results_table.setItem(row, 3, QTableWidgetItem(tool.get("version", "1.0.0")))
|
||||
self.results_table.setItem(row, 1, name_item)
|
||||
|
||||
# Owner
|
||||
self.results_table.setItem(row, 2, QTableWidgetItem(tool.get("owner", "")))
|
||||
|
||||
# Rating (stars)
|
||||
rating = tool.get("average_rating", 0) or 0
|
||||
rating_count = tool.get("rating_count", 0) or 0
|
||||
stars = self._rating_to_stars(rating)
|
||||
rating_item = QTableWidgetItem(f"{stars} ({rating_count})")
|
||||
rating_item.setToolTip(f"{rating:.1f}/5 from {rating_count} ratings")
|
||||
self.results_table.setItem(row, 3, rating_item)
|
||||
|
||||
# Downloads
|
||||
downloads = tool.get("downloads", 0)
|
||||
downloads_str = self._format_downloads(downloads)
|
||||
self.results_table.setItem(row, 4, QTableWidgetItem(downloads_str))
|
||||
|
||||
# Version
|
||||
self.results_table.setItem(row, 5, QTableWidgetItem(tool.get("version", "1.0.0")))
|
||||
|
||||
def _rating_to_stars(self, rating: float) -> str:
|
||||
"""Convert rating to star display."""
|
||||
if rating <= 0:
|
||||
return "☆☆☆☆☆"
|
||||
full_stars = int(rating)
|
||||
half_star = rating - full_stars >= 0.5
|
||||
empty_stars = 5 - full_stars - (1 if half_star else 0)
|
||||
return "★" * full_stars + ("½" if half_star else "") + "☆" * empty_stars
|
||||
|
||||
def _format_downloads(self, downloads: int) -> str:
|
||||
"""Format download count."""
|
||||
if downloads >= 1000000:
|
||||
return f"{downloads/1000000:.1f}M"
|
||||
elif downloads >= 1000:
|
||||
return f"{downloads/1000:.1f}K"
|
||||
return str(downloads)
|
||||
|
||||
def _on_search_error(self, error: str):
|
||||
"""Handle search error."""
|
||||
self.btn_search.setEnabled(True)
|
||||
self.btn_browse.setEnabled(True)
|
||||
self.status_label.setText(f"Error: {error}")
|
||||
|
||||
def _prev_page(self):
|
||||
"""Go to previous page."""
|
||||
if self._current_page > 1:
|
||||
self._current_page -= 1
|
||||
self._do_search()
|
||||
|
||||
def _next_page(self):
|
||||
"""Go to next page."""
|
||||
if self._current_page < self._total_pages:
|
||||
self._current_page += 1
|
||||
self._do_search()
|
||||
|
||||
def _on_selection_changed(self):
|
||||
"""Handle selection change."""
|
||||
items = self.results_table.selectedItems()
|
||||
|
|
@ -188,35 +396,141 @@ class RegistryPage(QWidget):
|
|||
self._selected_tool = None
|
||||
self.details_text.clear()
|
||||
self.btn_install.setEnabled(False)
|
||||
self.btn_update.hide()
|
||||
return
|
||||
|
||||
row = items[0].row()
|
||||
name_item = self.results_table.item(row, 0)
|
||||
name_item = self.results_table.item(row, 1)
|
||||
tool = name_item.data(Qt.UserRole)
|
||||
self._selected_tool = tool
|
||||
self._show_tool_details(tool)
|
||||
self.btn_install.setEnabled(True)
|
||||
|
||||
# Check if installed / update available
|
||||
tool_name = tool.get("name", "")
|
||||
if tool_name in self._installed_tools:
|
||||
installed_version = self._installed_tools[tool_name]
|
||||
registry_version = tool.get("version", "1.0.0")
|
||||
if installed_version != registry_version:
|
||||
self.btn_install.hide()
|
||||
self.btn_update.show()
|
||||
self.btn_update.setEnabled(True)
|
||||
self.btn_update.setText(f"Update ({installed_version} → {registry_version})")
|
||||
else:
|
||||
self.btn_install.setText("Reinstall")
|
||||
self.btn_install.setEnabled(True)
|
||||
self.btn_install.show()
|
||||
self.btn_update.hide()
|
||||
else:
|
||||
self.btn_install.setText("Install")
|
||||
self.btn_install.setEnabled(True)
|
||||
self.btn_install.show()
|
||||
self.btn_update.hide()
|
||||
|
||||
def _show_tool_details(self, tool: dict):
|
||||
"""Show tool details."""
|
||||
lines = []
|
||||
lines.append(f"<h2>{tool.get('owner', '')}/{tool.get('name', '')}</h2>")
|
||||
owner = tool.get('owner', '')
|
||||
name = tool.get('name', '')
|
||||
|
||||
lines.append(f"<h2>{owner}/{name}</h2>")
|
||||
|
||||
if tool.get("description"):
|
||||
lines.append(f"<p>{tool.get('description')}</p>")
|
||||
lines.append(f"<p style='color: #4a5568;'>{tool.get('description')}</p>")
|
||||
|
||||
# Rating display
|
||||
rating = tool.get("average_rating", 0) or 0
|
||||
rating_count = tool.get("rating_count", 0) or 0
|
||||
stars = self._rating_to_stars(rating)
|
||||
lines.append(f"<p><strong>Rating:</strong> {stars} ({rating:.1f}/5 from {rating_count} reviews)</p>")
|
||||
|
||||
lines.append(f"<p><strong>Version:</strong> {tool.get('version', '1.0.0')}</p>")
|
||||
lines.append(f"<p><strong>Downloads:</strong> {tool.get('downloads', 0)}</p>")
|
||||
lines.append(f"<p><strong>Downloads:</strong> {tool.get('downloads', 0):,}</p>")
|
||||
|
||||
if tool.get("category"):
|
||||
lines.append(f"<p><strong>Category:</strong> {tool.get('category')}</p>")
|
||||
|
||||
# Clickable tags
|
||||
if tool.get("tags"):
|
||||
tags = ", ".join(tool.get("tags", []))
|
||||
lines.append(f"<p><strong>Tags:</strong> {tags}</p>")
|
||||
tags_html = []
|
||||
for tag in tool.get("tags", []):
|
||||
tags_html.append(
|
||||
f'<a href="tag:{tag}" style="background: #edf2f7; padding: 2px 8px; '
|
||||
f'border-radius: 4px; text-decoration: none; color: #4a5568; margin-right: 4px;">{tag}</a>'
|
||||
)
|
||||
lines.append(f"<p><strong>Tags:</strong> {''.join(tags_html)}</p>")
|
||||
|
||||
# Publisher info
|
||||
if tool.get("publisher_reputation"):
|
||||
rep = tool.get("publisher_reputation", {})
|
||||
lines.append(f"<p style='margin-top: 16px; color: #718096; font-size: 12px;'>"
|
||||
f"Publisher: @{owner} · {rep.get('total_tools', 0)} tools · "
|
||||
f"{rep.get('total_downloads', 0):,} total downloads</p>")
|
||||
|
||||
# Installed status
|
||||
tool_name = tool.get("name", "")
|
||||
if tool_name in self._installed_tools:
|
||||
installed_version = self._installed_tools[tool_name]
|
||||
registry_version = tool.get("version", "1.0.0")
|
||||
if installed_version != registry_version:
|
||||
lines.append(f"<p style='color: #48bb78; font-weight: 600;'>"
|
||||
f"✓ Installed (v{installed_version}) - Update available!</p>")
|
||||
else:
|
||||
lines.append(f"<p style='color: #4299e1;'>✓ Installed (v{installed_version})</p>")
|
||||
|
||||
self.details_text.setHtml("\n".join(lines))
|
||||
|
||||
def _on_tag_clicked(self, url):
|
||||
"""Handle tag click to filter by tag."""
|
||||
tag = url.toString().replace("tag:", "")
|
||||
if tag not in self._current_tags:
|
||||
self._current_tags.append(tag)
|
||||
self._update_tags_display()
|
||||
self._current_page = 1
|
||||
self._do_search()
|
||||
|
||||
def _update_tags_display(self):
|
||||
"""Update the active tags display."""
|
||||
# Clear existing tag buttons
|
||||
while self.tags_layout.count() > 2:
|
||||
item = self.tags_layout.takeAt(2)
|
||||
if item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
if self._current_tags:
|
||||
self.tags_widget.show()
|
||||
for tag in self._current_tags:
|
||||
tag_btn = QPushButton(f"{tag} ×")
|
||||
tag_btn.setStyleSheet(
|
||||
"background: #4299e1; color: white; border: none; "
|
||||
"padding: 4px 8px; border-radius: 4px;"
|
||||
)
|
||||
tag_btn.clicked.connect(lambda checked, t=tag: self._remove_tag(t))
|
||||
self.tags_layout.insertWidget(self.tags_layout.count() - 1, tag_btn)
|
||||
|
||||
# Add clear all button
|
||||
clear_btn = QPushButton("Clear all")
|
||||
clear_btn.setStyleSheet("color: #e53e3e;")
|
||||
clear_btn.setFlat(True)
|
||||
clear_btn.clicked.connect(self._clear_tags)
|
||||
self.tags_layout.insertWidget(self.tags_layout.count() - 1, clear_btn)
|
||||
else:
|
||||
self.tags_widget.hide()
|
||||
|
||||
def _remove_tag(self, tag: str):
|
||||
"""Remove a tag from the filter."""
|
||||
if tag in self._current_tags:
|
||||
self._current_tags.remove(tag)
|
||||
self._update_tags_display()
|
||||
self._current_page = 1
|
||||
self._do_search()
|
||||
|
||||
def _clear_tags(self):
|
||||
"""Clear all tag filters."""
|
||||
self._current_tags = []
|
||||
self._update_tags_display()
|
||||
self._current_page = 1
|
||||
self._do_search()
|
||||
|
||||
def _install_tool(self):
|
||||
"""Install selected tool."""
|
||||
if not self._selected_tool:
|
||||
|
|
@ -226,6 +540,7 @@ class RegistryPage(QWidget):
|
|||
name = self._selected_tool.get("name", "")
|
||||
|
||||
self.btn_install.setEnabled(False)
|
||||
self.btn_update.setEnabled(False)
|
||||
self.status_label.setText(f"Installing {owner}/{name}...")
|
||||
|
||||
self._install_worker = InstallWorker(owner, name)
|
||||
|
|
@ -238,10 +553,23 @@ class RegistryPage(QWidget):
|
|||
self.btn_install.setEnabled(True)
|
||||
self.status_label.setText(f"Installed {tool_id}")
|
||||
self.main_window.show_status(f"Installed {tool_id}")
|
||||
|
||||
# Refresh installed tools list
|
||||
self._load_installed_tools()
|
||||
|
||||
# Update the display
|
||||
if self._selected_tool:
|
||||
self._show_tool_details(self._selected_tool)
|
||||
self._on_selection_changed()
|
||||
|
||||
# Refresh the table to update indicators
|
||||
self._do_search()
|
||||
|
||||
QMessageBox.information(self, "Success", f"Successfully installed {tool_id}")
|
||||
|
||||
def _on_install_error(self, error: str):
|
||||
"""Handle install error."""
|
||||
self.btn_install.setEnabled(True)
|
||||
self.btn_update.setEnabled(True)
|
||||
self.status_label.setText(f"Error: {error}")
|
||||
QMessageBox.critical(self, "Install Error", f"Failed to install tool:\n{error}")
|
||||
|
|
|
|||
Loading…
Reference in New Issue