CI / test (push) Canceled after 0s
## Summary What changed and why? ## Change Type - [ ] Cowork feature - [ ] Bug fix - [x] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Co-authored-by: NamPDT <minhanhpkpro@gmail.com> Reviewed-on: #3
157 lines
6.2 KiB
Python
157 lines
6.2 KiB
Python
"""Icons — a Monitoring sub-tab to browse the built-in icon set and add custom
|
|
icons for agents / flows.
|
|
|
|
Shows the built-in glyphs (the names usable in a Co4E step/agent ``icon`` field)
|
|
and the user's own imported SVG icons, with Add / Delete. Custom icons are saved
|
|
via ``core/custom_icons.py`` and become usable by name immediately.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from PySide6.QtCore import QSize, Qt
|
|
from PySide6.QtWidgets import (
|
|
QHBoxLayout, QInputDialog, QLabel, QLineEdit, QListWidget, QListWidgetItem,
|
|
QMessageBox, QPushButton, QVBoxLayout, QWidget,
|
|
)
|
|
|
|
from ..core import custom_icons
|
|
from ..i18n import on_language_changed, tr
|
|
from ..state import AppContext
|
|
from . import icons as icons_mod
|
|
from .icons import icon
|
|
|
|
|
|
def _grid() -> QListWidget:
|
|
g = QListWidget()
|
|
g.setObjectName("iconGrid") # accent border on hover/selection, see theme.py
|
|
g.setViewMode(QListWidget.IconMode)
|
|
g.setResizeMode(QListWidget.Adjust)
|
|
g.setMovement(QListWidget.Static)
|
|
g.setIconSize(QSize(28, 28))
|
|
g.setGridSize(QSize(96, 74))
|
|
g.setSpacing(4)
|
|
return g
|
|
|
|
|
|
class IconsAdminTab(QWidget):
|
|
def __init__(self, ctx: AppContext):
|
|
super().__init__()
|
|
self.ctx = ctx
|
|
root = QVBoxLayout(self)
|
|
# Header row: the three actions sit beside the title, where the drawing
|
|
# puts them, instead of in a strip below the two grids where they read
|
|
# as belonging to the custom grid alone.
|
|
head = QHBoxLayout()
|
|
self._title = QLabel()
|
|
self._title.setObjectName("monTitle")
|
|
head.addWidget(self._title)
|
|
head.addStretch(1)
|
|
self.add_btn = QPushButton(); self.add_btn.setIcon(icon("plus"))
|
|
self.add_btn.clicked.connect(self._add_icon)
|
|
self.paste_btn = QPushButton()
|
|
self.paste_btn.clicked.connect(self._add_from_svg_text)
|
|
self.del_btn = QPushButton(); self.del_btn.setIcon(icon("trash"))
|
|
self.del_btn.clicked.connect(self._delete_icon)
|
|
for b in (self.add_btn, self.paste_btn, self.del_btn):
|
|
head.addWidget(b)
|
|
root.addLayout(head)
|
|
|
|
self._hint = QLabel(); self._hint.setObjectName("hint"); self._hint.setWordWrap(True)
|
|
root.addWidget(self._hint)
|
|
|
|
# search over built-in names, with the magnifier the drawing asks for
|
|
self.search = QLineEdit()
|
|
self.search.addAction(icon("search"), QLineEdit.LeadingPosition)
|
|
self.search.textChanged.connect(self._reload_builtin)
|
|
root.addWidget(self.search)
|
|
|
|
self._builtin_lbl = QLabel()
|
|
self._builtin_lbl.setObjectName("navSectionHdr") # quiet caps heading
|
|
root.addWidget(self._builtin_lbl)
|
|
self.builtin_grid = _grid()
|
|
root.addWidget(self.builtin_grid, 2)
|
|
|
|
self._custom_lbl = QLabel()
|
|
self._custom_lbl.setObjectName("navSectionHdr")
|
|
root.addWidget(self._custom_lbl)
|
|
self.custom_grid = _grid()
|
|
root.addWidget(self.custom_grid, 1)
|
|
|
|
on_language_changed(self._retranslate)
|
|
self._retranslate()
|
|
|
|
# ---- rendering --------------------------------------------------------
|
|
def _reload_builtin(self, *_a) -> None:
|
|
q = self.search.text().strip().lower()
|
|
self.builtin_grid.clear()
|
|
for name in sorted(icons_mod._PATHS):
|
|
if q and q not in name:
|
|
continue
|
|
it = QListWidgetItem(icon(name), name)
|
|
it.setToolTip(name)
|
|
it.setTextAlignment(Qt.AlignHCenter | Qt.AlignBottom)
|
|
self.builtin_grid.addItem(it)
|
|
|
|
def _reload_custom(self) -> None:
|
|
self.custom_grid.clear()
|
|
for name in custom_icons.list_custom():
|
|
it = QListWidgetItem(icon(name), name)
|
|
it.setToolTip(name)
|
|
it.setData(Qt.UserRole, name)
|
|
it.setTextAlignment(Qt.AlignHCenter | Qt.AlignBottom)
|
|
self.custom_grid.addItem(it)
|
|
|
|
# ---- actions ----------------------------------------------------------
|
|
def _add_icon(self) -> None:
|
|
from PySide6.QtWidgets import QFileDialog
|
|
path, _ = QFileDialog.getOpenFileName(self, tr("icons_admin.add"), "", "SVG (*.svg)")
|
|
if not path:
|
|
return
|
|
name, ok = QInputDialog.getText(self, tr("icons_admin.name_prompt"),
|
|
tr("icons_admin.name_prompt"))
|
|
if not ok:
|
|
return
|
|
try:
|
|
custom_icons.add_from_file(path, name.strip())
|
|
except (OSError, ValueError) as exc:
|
|
QMessageBox.warning(self, tr("icons_admin.title"), str(exc))
|
|
return
|
|
self._reload_custom()
|
|
|
|
def _add_from_svg_text(self) -> None:
|
|
name, ok = QInputDialog.getText(self, tr("icons_admin.name_prompt"),
|
|
tr("icons_admin.name_prompt"))
|
|
if not ok or not name.strip():
|
|
return
|
|
svg, ok = QInputDialog.getMultiLineText(self, tr("icons_admin.paste"),
|
|
tr("icons_admin.paste_prompt"))
|
|
if not ok:
|
|
return
|
|
try:
|
|
custom_icons.add_svg(name.strip(), svg)
|
|
except ValueError as exc:
|
|
QMessageBox.warning(self, tr("icons_admin.title"), str(exc))
|
|
return
|
|
self._reload_custom()
|
|
|
|
def _delete_icon(self) -> None:
|
|
item = self.custom_grid.currentItem()
|
|
if item is None:
|
|
QMessageBox.information(self, tr("icons_admin.title"), tr("icons_admin.select_custom"))
|
|
return
|
|
custom_icons.delete_custom(item.data(Qt.UserRole))
|
|
self._reload_custom()
|
|
|
|
def _retranslate(self) -> None:
|
|
self._title.setText(tr("monitoring.tab_icons"))
|
|
self._hint.setText(tr("icons_admin.hint"))
|
|
self.search.setPlaceholderText(tr("icons_admin.search"))
|
|
# ICON TÍCH HỢP / ICON TÙY CHỈNH — caps, like every other section
|
|
# heading the audit page draws.
|
|
self._builtin_lbl.setText(tr("icons_admin.builtin").upper())
|
|
self._custom_lbl.setText(tr("icons_admin.custom").upper())
|
|
self.add_btn.setText(tr("icons_admin.add"))
|
|
self.paste_btn.setText(tr("icons_admin.paste"))
|
|
self.del_btn.setText(tr("icons_admin.delete"))
|
|
self._reload_builtin()
|
|
self._reload_custom()
|