"""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, QVBoxLayout, QWidget, ) from ...i18n import on_language_changed, tr from ...ui.icons import icon #: (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) 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() 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)) 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) grid_host = QWidget() grid_host.setMaximumWidth(460) grid = QGridLayout(grid_host) grid.setContentsMargins(0, 0, 0, 0) 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))) grid.addWidget(card, i // 2, i % 2) self.cards.append(card) grid_row.addWidget(grid_host) grid_row.addStretch(1) root.addLayout(grid_row) root.addStretch(2) on_language_changed(self._retranslate) # ---- nội dung ---------------------------------------------------------- def refresh(self, user_name: str = "", project: str = "", files: int = -1, skills: int = -1) -> None: """Cập nhật lời chào và dòng bối cảnh. ``files`` / ``skills`` 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. """ 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)) if skills >= 0: parts.append(tr("welcome.meta_skills", n=skills)) 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()