fix các bug theo yêu cầu https://fptsoftware362-my.sharepoint.com/❌/g/personal/nampdt_fpt_com/IQAHBJ4A9xqDTLgvt2bhukJEAdRB5LRz2hbJpTivvIiBSYM?wdExp=TEAMS-TREATMENT&web=1&isSPOFile=1&ovuser=f01e930a-b52e-42b1-b70f-a8882b5d043b%2CAnhTNM1%40fpt.com&clickparams=eyJBcHBOYW1lIjoiVGVhbXMtRGVza3RvcCIsIkFwcFZlcnNpb24iOiI0OS8yNjA4MTMxOTMxNyIsIkhhc0ZlZGVyYXRlZFVzZXIiOmZhbHNlfQ%3D%3D --------- Co-authored-by: Duy Le Huu <duylh19@fpt.com> Reviewed-on: #10 Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com>
This commit was merged in pull request #10.
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
"""Màn giới thiệu của khung chat khi hội thoại còn rỗng.
|
||||
|
||||
Bấm "Cuộc trò chuyện mới" trước đây để lại một khung trắng: không có gì nói
|
||||
người dùng đang làm trong project nào, thư mục có bao nhiêu tệp, hay bắt đầu từ
|
||||
đâu. Đây là trạng thái RỖNG — một trong bốn trạng thái mà mọi khung dữ liệu phải
|
||||
có (xem ``agent/checklist/ux_review.md``), và là trạng thái duy nhất người dùng
|
||||
nhìn thấy trước khi họ gõ chữ đầu tiên.
|
||||
|
||||
Bốn thẻ gợi ý ĐIỀN vào ô nhập chứ không gửi luôn: câu gợi ý là điểm bắt đầu, và
|
||||
người dùng gần như luôn cần thêm chi tiết của riêng họ trước khi gửi. Gửi ngay
|
||||
sẽ tiêu một lượt gọi model cho một câu hỏi chung chung.
|
||||
|
||||
Dấu trang trí phía trên lời chào không bấm được — nó là một dấu hiệu thị giác,
|
||||
không phải nút. Một nút không làm gì tệ hơn không có nút.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QGridLayout, QHBoxLayout, QLabel, QPushButton, QSizePolicy, QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from ...i18n import on_language_changed, tr
|
||||
from ...ui.icons import icon
|
||||
|
||||
#: Width of the four-card block. A FLOOR for the cap, not a fixed number: the
|
||||
#: block never gets narrower than this, but the cap grows when the text needs
|
||||
#: more room. One number measured against English at 100% scale is exactly how
|
||||
#: the titles end up clipped in Vietnamese and Japanese (``qt_pitfalls.md`` P02).
|
||||
_GRID_WIDTH_FLOOR = 460
|
||||
|
||||
#: (khoá tiêu đề, khoá mô tả, khoá câu gợi ý, tên icon) cho từng thẻ.
|
||||
_CARDS = (
|
||||
("welcome.card_docs", "welcome.card_docs_sub", "welcome.prompt_docs", "file"),
|
||||
("welcome.card_data", "welcome.card_data_sub", "welcome.prompt_data", "table"),
|
||||
("welcome.card_schedule", "welcome.card_schedule_sub", "welcome.prompt_schedule", "schedule"),
|
||||
("welcome.card_graph", "welcome.card_graph_sub", "welcome.prompt_graph", "graph"),
|
||||
)
|
||||
|
||||
|
||||
class _Card(QPushButton):
|
||||
"""Một thẻ gợi ý: icon, tiêu đề, và một dòng mô tả bên dưới."""
|
||||
|
||||
def __init__(self, title_key: str, sub_key: str, icon_name: str,
|
||||
parent: QWidget | None = None) -> None:
|
||||
"""Thẻ gợi ý. Là ``QPushButton`` để có sẵn hover, focus và bàn phím."""
|
||||
super().__init__(parent)
|
||||
self._title_key = title_key
|
||||
self._sub_key = sub_key
|
||||
self.setObjectName("welcomeCard")
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
# Vertically it must be able to GROW: QPushButton defaults to Fixed, so
|
||||
# a card whose description fits on one line was centred inside a row as
|
||||
# tall as its two-line neighbour — two cards side by side, staggered and
|
||||
# of different heights.
|
||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)
|
||||
|
||||
row = QHBoxLayout(self)
|
||||
row.setContentsMargins(12, 10, 12, 10)
|
||||
row.setSpacing(10)
|
||||
|
||||
self._icon = QLabel()
|
||||
self._icon.setPixmap(icon(icon_name).pixmap(18, 18))
|
||||
self._icon.setAlignment(Qt.AlignTop)
|
||||
row.addWidget(self._icon)
|
||||
|
||||
col = QVBoxLayout()
|
||||
col.setContentsMargins(0, 0, 0, 0)
|
||||
col.setSpacing(2)
|
||||
self.title_label = QLabel()
|
||||
self.title_label.setObjectName("welcomeCardTitle")
|
||||
self.sub_label = QLabel()
|
||||
self.sub_label.setObjectName("hint")
|
||||
self.sub_label.setWordWrap(True)
|
||||
col.addWidget(self.title_label)
|
||||
col.addWidget(self.sub_label)
|
||||
row.addLayout(col, 1)
|
||||
|
||||
self.retranslate()
|
||||
|
||||
# ---- size: taken from the child layout, not from the button's own text -- #
|
||||
# QPushButton computes sizeHint/minimumSizeHint from ITS OWN text and icon
|
||||
# and ignores the child layout. This card leaves both of those empty on
|
||||
# purpose (the two QLabels below draw the text; a non-empty text() prints
|
||||
# on top of them), so the button reported 54x15 while its layout asked for
|
||||
# 258x48 — the two QLabels and the icon cell were handed 0px of height, and
|
||||
# what the user saw was four empty frames with no text and no icon. The two
|
||||
# overrides below report the size the content actually needs.
|
||||
|
||||
def sizeHint(self): # noqa: N802 - Qt override
|
||||
"""Size the card's own content needs, not the (empty) button label."""
|
||||
return self.layout().sizeHint()
|
||||
|
||||
def minimumSizeHint(self): # noqa: N802 - Qt override
|
||||
"""Floor comes from the child layout, for the same reason."""
|
||||
return self.layout().minimumSize()
|
||||
|
||||
def retranslate(self) -> None:
|
||||
"""Áp lại chữ theo ngôn ngữ đang chọn."""
|
||||
self.title_label.setText(tr(self._title_key))
|
||||
self.sub_label.setText(tr(self._sub_key))
|
||||
# Nhãn của chính QPushButton để rỗng — chữ do hai QLabel bên trong vẽ,
|
||||
# đặt cả hai chỗ sẽ in đè lên nhau.
|
||||
self.setAccessibleName(tr(self._title_key))
|
||||
# New text means a new content size — Japanese and Vietnamese are not
|
||||
# the same length, and sizeHint is computed from those two QLabels.
|
||||
self.updateGeometry()
|
||||
|
||||
|
||||
class ChatWelcome(QWidget):
|
||||
"""Trạng thái rỗng của khung chat: lời chào, dòng bối cảnh, bốn thẻ gợi ý."""
|
||||
|
||||
#: Người dùng chọn một thẻ. Mang theo câu gợi ý để chỗ nhận điền vào ô nhập.
|
||||
suggestion_picked = Signal(str)
|
||||
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
"""Dựng màn giới thiệu. Chữ được điền qua :meth:`refresh`."""
|
||||
super().__init__(parent)
|
||||
self.setObjectName("chatWelcome")
|
||||
self._user_name = ""
|
||||
self._meta_parts: list = []
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
root.setContentsMargins(24, 24, 24, 24)
|
||||
root.addStretch(1)
|
||||
|
||||
mark_row = QHBoxLayout()
|
||||
mark_row.addStretch(1)
|
||||
self._mark = QLabel()
|
||||
self._mark.setObjectName("welcomeMark")
|
||||
self._mark.setPixmap(icon("sparkle").pixmap(20, 20))
|
||||
self._mark.setAlignment(Qt.AlignCenter)
|
||||
self._mark.setFixedSize(38, 38)
|
||||
mark_row.addWidget(self._mark)
|
||||
mark_row.addStretch(1)
|
||||
root.addLayout(mark_row)
|
||||
root.addSpacing(14)
|
||||
|
||||
self.greeting_label = QLabel()
|
||||
self.greeting_label.setObjectName("welcomeGreeting")
|
||||
self.greeting_label.setAlignment(Qt.AlignCenter)
|
||||
root.addWidget(self.greeting_label)
|
||||
|
||||
self.meta_label = QLabel()
|
||||
self.meta_label.setObjectName("hint")
|
||||
self.meta_label.setAlignment(Qt.AlignCenter)
|
||||
root.addWidget(self.meta_label)
|
||||
root.addSpacing(18)
|
||||
|
||||
grid_row = QHBoxLayout()
|
||||
grid_row.addStretch(1)
|
||||
self._grid_host = QWidget()
|
||||
self._grid = QGridLayout(self._grid_host)
|
||||
self._grid.setContentsMargins(0, 0, 0, 0)
|
||||
self._grid.setSpacing(10)
|
||||
self.cards: list = []
|
||||
for i, (title_key, sub_key, prompt_key, icon_name) in enumerate(_CARDS):
|
||||
card = _Card(title_key, sub_key, icon_name)
|
||||
card.clicked.connect(
|
||||
lambda _checked=False, key=prompt_key: self.suggestion_picked.emit(tr(key)))
|
||||
self._grid.addWidget(card, i // 2, i % 2)
|
||||
self.cards.append(card)
|
||||
self._apply_grid_width()
|
||||
grid_row.addWidget(self._grid_host)
|
||||
grid_row.addStretch(1)
|
||||
root.addLayout(grid_row)
|
||||
|
||||
root.addStretch(2)
|
||||
|
||||
on_language_changed(self._retranslate)
|
||||
|
||||
def _apply_grid_width(self) -> None:
|
||||
"""Cap the card block at the wider of the design width and what text needs.
|
||||
|
||||
Recomputed on every language change: ``vi`` and ``ja`` labels are not
|
||||
the same length as ``en``, and a cap fixed at build time clips whichever
|
||||
language happens to be longer.
|
||||
"""
|
||||
self._grid_host.setMaximumWidth(
|
||||
max(_GRID_WIDTH_FLOOR, self._grid.sizeHint().width()))
|
||||
|
||||
# ---- nội dung ----------------------------------------------------------
|
||||
|
||||
def refresh(self, user_name: str = "", project: str = "",
|
||||
files: int = -1) -> None:
|
||||
"""Cập nhật lời chào và dòng bối cảnh.
|
||||
|
||||
``files`` bằng ``-1`` nghĩa là KHÔNG BIẾT, và phần đó bị bỏ khỏi dòng
|
||||
meta — thà thiếu một mảnh còn hơn hiện số 0 mà người dùng vừa thấy có
|
||||
tệp trong thư mục.
|
||||
|
||||
Không hiện số skill đang bật: nó không giúp người dùng quyết định gõ gì
|
||||
vào ô nhập, mà lại chiếm một phần ba của dòng bối cảnh.
|
||||
"""
|
||||
self._user_name = (user_name or "").strip()
|
||||
parts = []
|
||||
if (project or "").strip():
|
||||
parts.append(tr("welcome.meta_project", name=project.strip()))
|
||||
if files >= 0:
|
||||
parts.append(tr("welcome.meta_files", n=files))
|
||||
self._meta_parts = parts
|
||||
self._retranslate()
|
||||
|
||||
def _retranslate(self) -> None:
|
||||
"""Áp lại chữ theo ngôn ngữ đang chọn."""
|
||||
if self._user_name:
|
||||
self.greeting_label.setText(tr("welcome.greeting", name=self._user_name))
|
||||
else:
|
||||
self.greeting_label.setText(tr("welcome.greeting_anon"))
|
||||
self.meta_label.setText(" · ".join(self._meta_parts))
|
||||
self.meta_label.setVisible(bool(self._meta_parts))
|
||||
for card in self.cards:
|
||||
card.retranslate()
|
||||
self._apply_grid_width()
|
||||
Reference in New Issue
Block a user