Feature/fsg gamma team ui fix #3
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"kind": "cowork",
|
||||||
|
"session_id": "20260818-130950-831",
|
||||||
|
"title": "alo",
|
||||||
|
"created": "2026-08-18T13:10:03",
|
||||||
|
"pinned": false,
|
||||||
|
"project_id": "project-mới",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [],
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "alo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2472,8 +2472,8 @@ STRINGS: Dict[str, Dict[str, str]] = {
|
|||||||
"下から独自のSVGアイコンを追加でき、名前ですぐ使えます。",
|
"下から独自のSVGアイコンを追加でき、名前ですぐ使えます。",
|
||||||
"vi": "Các icon dùng cho agent và flow. Gõ tên vào ô Icon của step/agent để dùng. Thêm icon SVG "
|
"vi": "Các icon dùng cho agent và flow. Gõ tên vào ô Icon của step/agent để dùng. Thêm icon SVG "
|
||||||
"của bạn ở dưới — dùng được ngay bằng tên."},
|
"của bạn ở dưới — dùng được ngay bằng tên."},
|
||||||
"icons_admin.search": {"en": "Search built-in icons…", "ja": "組込みアイコンを検索…", "vi": "Tìm icon có sẵn…"},
|
"icons_admin.search": {"en": "Search icons by name…", "ja": "名前でアイコンを検索…", "vi": "Tìm icon theo tên…"},
|
||||||
"icons_admin.builtin": {"en": "Built-in icons", "ja": "組込みアイコン", "vi": "Icon có sẵn"},
|
"icons_admin.builtin": {"en": "Built-in icons", "ja": "組込みアイコン", "vi": "Icon tích hợp"},
|
||||||
"icons_admin.custom": {"en": "Custom icons", "ja": "カスタムアイコン", "vi": "Icon tùy chỉnh"},
|
"icons_admin.custom": {"en": "Custom icons", "ja": "カスタムアイコン", "vi": "Icon tùy chỉnh"},
|
||||||
"icons_admin.add": {"en": "Add SVG file", "ja": "SVGファイルを追加", "vi": "Thêm tệp SVG"},
|
"icons_admin.add": {"en": "Add SVG file", "ja": "SVGファイルを追加", "vi": "Thêm tệp SVG"},
|
||||||
"icons_admin.paste": {"en": "Paste SVG", "ja": "SVGを貼付", "vi": "Dán SVG"},
|
"icons_admin.paste": {"en": "Paste SVG", "ja": "SVGを貼付", "vi": "Dán SVG"},
|
||||||
|
|||||||
@@ -458,6 +458,23 @@ QLabel#navSectionHdr {
|
|||||||
padding: 8px 8px 2px 8px; background: transparent;
|
padding: 8px 8px 2px 8px; background: transparent;
|
||||||
}
|
}
|
||||||
QTreeWidget#navRecents { border-top: 1px solid $nav_border; }
|
QTreeWidget#navRecents { border-top: 1px solid $nav_border; }
|
||||||
|
|
||||||
|
/* Icon library cells. The audit page's note on this screen is that the cells
|
||||||
|
had no visible edge on hover or selection, so you could not tell what you
|
||||||
|
were about to pick — "cùng ngôn ngữ thẻ với Agent/Công cụ". */
|
||||||
|
QListWidget#iconGrid { background: transparent; border: none; }
|
||||||
|
QListWidget#iconGrid::item {
|
||||||
|
border: 1px solid transparent; border-radius: ${radius}px;
|
||||||
|
color: $text_muted; padding: 4px;
|
||||||
|
}
|
||||||
|
QListWidget#iconGrid::item:hover {
|
||||||
|
border: 1px solid $accent; background: $hover; color: $text;
|
||||||
|
}
|
||||||
|
QListWidget#iconGrid::item:selected {
|
||||||
|
border: 1px solid $accent; background: $accent_wash; color: $text;
|
||||||
|
}
|
||||||
|
/* Screen title beside its actions, same weight the other admin screens use. */
|
||||||
|
QLabel#monTitle { font-size: 15px; font-weight: 700; color: $text; }
|
||||||
/* Rail header — the primary action, so it is the one filled button up there. */
|
/* Rail header — the primary action, so it is the one filled button up there. */
|
||||||
QPushButton#navNewChatBtn {
|
QPushButton#navNewChatBtn {
|
||||||
background: $accent_solid; color: #FFFFFF; border: none; font-weight: 600;
|
background: $accent_solid; color: #FFFFFF; border: none; font-weight: 600;
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
"""The Icon library screen against its wireframe.
|
||||||
|
|
||||||
|
The drawing (section 16) puts the three actions on the title row, a magnifier
|
||||||
|
in the search box, caps section headings, and — its stated complaint — a
|
||||||
|
visible edge on an icon cell when you hover or select it, so you can tell what
|
||||||
|
you are about to pick.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(REPO.parent))
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||||
|
|
||||||
|
from capture_screens import ( # noqa: E402
|
||||||
|
_apply_theme, _freeze_schedulers, _isolate_home, _load_fonts)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
sandbox = _isolate_home()
|
||||||
|
from PySide6.QtCore import QPoint
|
||||||
|
from PySide6.QtWidgets import QApplication, QLineEdit
|
||||||
|
|
||||||
|
app = QApplication([])
|
||||||
|
_load_fonts()
|
||||||
|
_freeze_schedulers()
|
||||||
|
_apply_theme(app)
|
||||||
|
|
||||||
|
from cowork_local.config import CONFIG_DIR
|
||||||
|
assert str(sandbox) in str(CONFIG_DIR), f"isolation failed: {CONFIG_DIR}"
|
||||||
|
|
||||||
|
from cowork_local.config import AppConfig
|
||||||
|
from cowork_local.i18n import set_language
|
||||||
|
from cowork_local.state import AppContext
|
||||||
|
from cowork_local.ui.icons_admin_tab import IconsAdminTab
|
||||||
|
|
||||||
|
set_language("vi")
|
||||||
|
tab = IconsAdminTab(AppContext(AppConfig.load()))
|
||||||
|
tab.resize(1100, 700)
|
||||||
|
tab.show()
|
||||||
|
app.processEvents()
|
||||||
|
|
||||||
|
fails = []
|
||||||
|
|
||||||
|
# 1. actions on the title row, to its right — not in a strip below the
|
||||||
|
# grids. Comparing y alone was not enough: a button left out of the
|
||||||
|
# layout sits at (0,0), which is "the same row" by accident.
|
||||||
|
title = tab._title
|
||||||
|
t_pos = title.mapTo(tab, QPoint(0, 0))
|
||||||
|
t_mid = t_pos.y() + title.height() // 2
|
||||||
|
t_right = t_pos.x() + title.width()
|
||||||
|
for name, btn in (("Thêm", tab.add_btn), ("Dán", tab.paste_btn),
|
||||||
|
("Xóa", tab.del_btn)):
|
||||||
|
pos = btn.mapTo(tab, QPoint(0, 0))
|
||||||
|
mid = pos.y() + btn.height() // 2
|
||||||
|
aligned = abs(mid - t_mid) <= 6
|
||||||
|
after = pos.x() >= t_right
|
||||||
|
print(f"nut {name:5}: tam y={mid} (tieu de {t_mid}) thang hang={aligned} "
|
||||||
|
f"| x={pos.x()} (sau tieu de {t_right})={after}")
|
||||||
|
if not (aligned and after):
|
||||||
|
fails.append(f"nut {name} khong nam cung hang, ben phai tieu de")
|
||||||
|
|
||||||
|
# 2. magnifier in the search box
|
||||||
|
lead = tab.search.actions()
|
||||||
|
print(f"o tim co icon kinh lup: {bool(lead)}")
|
||||||
|
if not lead:
|
||||||
|
fails.append("o tim thieu icon kinh lup")
|
||||||
|
|
||||||
|
# 3. caps headings
|
||||||
|
for name, lbl in (("tich hop", tab._builtin_lbl), ("tuy chinh", tab._custom_lbl)):
|
||||||
|
text = lbl.text()
|
||||||
|
print(f"tieu de {name}: {text!r}")
|
||||||
|
if text != text.upper():
|
||||||
|
fails.append(f"tieu de {name} chua viet hoa: {text!r}")
|
||||||
|
|
||||||
|
# 4. the cell has an edge to see — compare the painted cell hovered vs not
|
||||||
|
grid = tab.builtin_grid
|
||||||
|
if grid.count():
|
||||||
|
rect = grid.visualItemRect(grid.item(0))
|
||||||
|
plain = grid.grab(rect).toImage()
|
||||||
|
grid.setCurrentRow(0)
|
||||||
|
app.processEvents()
|
||||||
|
picked = grid.grab(rect).toImage()
|
||||||
|
diff = sum(1 for x in range(plain.width()) for y in range(plain.height())
|
||||||
|
if plain.pixelColor(x, y) != picked.pixelColor(x, y))
|
||||||
|
print(f"o icon doi {diff} diem anh khi duoc chon")
|
||||||
|
if diff == 0:
|
||||||
|
fails.append("o icon khong doi gi khi duoc chon — khong thay minh dang chon cai nao")
|
||||||
|
else:
|
||||||
|
fails.append("luoi icon rong")
|
||||||
|
|
||||||
|
print()
|
||||||
|
for f in fails:
|
||||||
|
print("FAIL " + f)
|
||||||
|
print("PASS man Icon khop ban ve" if not fails else f"{len(fails)} problem(s)")
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(1 if fails else 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
+38
-24
@@ -22,6 +22,7 @@ from .icons import icon
|
|||||||
|
|
||||||
def _grid() -> QListWidget:
|
def _grid() -> QListWidget:
|
||||||
g = QListWidget()
|
g = QListWidget()
|
||||||
|
g.setObjectName("iconGrid") # accent border on hover/selection, see theme.py
|
||||||
g.setViewMode(QListWidget.IconMode)
|
g.setViewMode(QListWidget.IconMode)
|
||||||
g.setResizeMode(QListWidget.Adjust)
|
g.setResizeMode(QListWidget.Adjust)
|
||||||
g.setMovement(QListWidget.Static)
|
g.setMovement(QListWidget.Static)
|
||||||
@@ -36,34 +37,44 @@ class IconsAdminTab(QWidget):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
root = QVBoxLayout(self)
|
root = QVBoxLayout(self)
|
||||||
self._hint = QLabel(); self._hint.setObjectName("hint"); self._hint.setWordWrap(True)
|
# Header row: the three actions sit beside the title, where the drawing
|
||||||
root.addWidget(self._hint)
|
# puts them, instead of in a strip below the two grids where they read
|
||||||
|
# as belonging to the custom grid alone.
|
||||||
# search over built-in names
|
head = QHBoxLayout()
|
||||||
self.search = QLineEdit()
|
self._title = QLabel()
|
||||||
self.search.textChanged.connect(self._reload_builtin)
|
self._title.setObjectName("monTitle")
|
||||||
root.addWidget(self.search)
|
head.addWidget(self._title)
|
||||||
|
head.addStretch(1)
|
||||||
self._builtin_lbl = QLabel()
|
|
||||||
root.addWidget(self._builtin_lbl)
|
|
||||||
self.builtin_grid = _grid()
|
|
||||||
root.addWidget(self.builtin_grid, 2)
|
|
||||||
|
|
||||||
self._custom_lbl = QLabel()
|
|
||||||
root.addWidget(self._custom_lbl)
|
|
||||||
self.custom_grid = _grid()
|
|
||||||
root.addWidget(self.custom_grid, 1)
|
|
||||||
|
|
||||||
btns = QHBoxLayout()
|
|
||||||
self.add_btn = QPushButton(); self.add_btn.setIcon(icon("plus"))
|
self.add_btn = QPushButton(); self.add_btn.setIcon(icon("plus"))
|
||||||
self.add_btn.clicked.connect(self._add_icon)
|
self.add_btn.clicked.connect(self._add_icon)
|
||||||
self.paste_btn = QPushButton()
|
self.paste_btn = QPushButton()
|
||||||
self.paste_btn.clicked.connect(self._add_from_svg_text)
|
self.paste_btn.clicked.connect(self._add_from_svg_text)
|
||||||
self.del_btn = QPushButton(); self.del_btn.setIcon(icon("trash"))
|
self.del_btn = QPushButton(); self.del_btn.setIcon(icon("trash"))
|
||||||
self.del_btn.clicked.connect(self._delete_icon)
|
self.del_btn.clicked.connect(self._delete_icon)
|
||||||
btns.addWidget(self.add_btn); btns.addWidget(self.paste_btn)
|
for b in (self.add_btn, self.paste_btn, self.del_btn):
|
||||||
btns.addWidget(self.del_btn); btns.addStretch(1)
|
head.addWidget(b)
|
||||||
root.addLayout(btns)
|
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)
|
on_language_changed(self._retranslate)
|
||||||
self._retranslate()
|
self._retranslate()
|
||||||
@@ -131,10 +142,13 @@ class IconsAdminTab(QWidget):
|
|||||||
self._reload_custom()
|
self._reload_custom()
|
||||||
|
|
||||||
def _retranslate(self) -> None:
|
def _retranslate(self) -> None:
|
||||||
|
self._title.setText(tr("monitoring.tab_icons"))
|
||||||
self._hint.setText(tr("icons_admin.hint"))
|
self._hint.setText(tr("icons_admin.hint"))
|
||||||
self.search.setPlaceholderText(tr("icons_admin.search"))
|
self.search.setPlaceholderText(tr("icons_admin.search"))
|
||||||
self._builtin_lbl.setText(tr("icons_admin.builtin"))
|
# ICON TÍCH HỢP / ICON TÙY CHỈNH — caps, like every other section
|
||||||
self._custom_lbl.setText(tr("icons_admin.custom"))
|
# 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.add_btn.setText(tr("icons_admin.add"))
|
||||||
self.paste_btn.setText(tr("icons_admin.paste"))
|
self.paste_btn.setText(tr("icons_admin.paste"))
|
||||||
self.del_btn.setText(tr("icons_admin.delete"))
|
self.del_btn.setText(tr("icons_admin.delete"))
|
||||||
|
|||||||
Reference in New Issue
Block a user