Delta team/fix comment ui #10
@@ -9,6 +9,64 @@ from __future__ import annotations
|
||||
from typing import Dict
|
||||
|
||||
STRINGS: Dict[str, Dict[str, str]] = {
|
||||
# ---- man gioi thieu cua khung chat (trang thai rong) ------------------
|
||||
"welcome.greeting": {
|
||||
"en": "Hi {name} — where would you like to start?",
|
||||
"ja": "{name} さん、どこから始めましょうか?",
|
||||
"vi": "Chào {name} — bắt đầu từ đâu?"},
|
||||
"welcome.greeting_anon": {
|
||||
"en": "Where would you like to start?", "ja": "どこから始めましょうか?",
|
||||
"vi": "Bắt đầu từ đâu?"},
|
||||
"welcome.meta_project": {
|
||||
"en": "Working in {name}", "ja": "{name} で作業中",
|
||||
"vi": "Đang làm trong {name}"},
|
||||
"welcome.meta_files": {
|
||||
"en": "{n} file(s) in the local folder", "ja": "ローカルフォルダに {n} 件",
|
||||
"vi": "{n} tệp trong thư mục local"},
|
||||
"welcome.meta_skills": {
|
||||
"en": "Skills: {n} on", "ja": "スキル: {n} 個オン", "vi": "Skills: {n} bật"},
|
||||
|
||||
"welcome.card_docs": {
|
||||
"en": "Summarise documents", "ja": "ドキュメントを要約", "vi": "Tóm tắt tài liệu"},
|
||||
"welcome.card_docs_sub": {
|
||||
"en": "Read the files in the local folder", "ja": "ローカルフォルダのファイルを読む",
|
||||
"vi": "Đọc các tệp trong thư mục local"},
|
||||
"welcome.prompt_docs": {
|
||||
"en": "Read the files in this project's folder and summarise each one.",
|
||||
"ja": "このプロジェクトのフォルダにあるファイルを読み、それぞれ要約してください。",
|
||||
"vi": "Đọc các tệp trong thư mục của project này và tóm tắt từng tệp."},
|
||||
|
||||
"welcome.card_data": {
|
||||
"en": "Analyse data", "ja": "データを分析", "vi": "Phân tích dữ liệu"},
|
||||
"welcome.card_data_sub": {
|
||||
"en": "Spreadsheets, CSV, logs", "ja": "表計算、CSV、ログ",
|
||||
"vi": "Bảng tính, CSV, log"},
|
||||
"welcome.prompt_data": {
|
||||
"en": "Analyse the spreadsheet/CSV/log files in this folder and report what stands out.",
|
||||
"ja": "このフォルダの表計算/CSV/ログを分析し、目立つ点を報告してください。",
|
||||
"vi": "Phân tích các tệp bảng tính/CSV/log trong thư mục này và nêu những điểm đáng chú ý."},
|
||||
|
||||
"welcome.card_schedule": {
|
||||
"en": "Set up a schedule", "ja": "スケジュールを作成", "vi": "Dựng lịch chạy"},
|
||||
"welcome.card_schedule_sub": {
|
||||
"en": "Create a daily Schedule Task", "ja": "毎日実行する Schedule Task を作成",
|
||||
"vi": "Tạo Schedule Task hàng ngày"},
|
||||
"welcome.prompt_schedule": {
|
||||
"en": "Help me set up a Schedule Task that runs every day. Ask me what it should do.",
|
||||
"ja": "毎日実行する Schedule Task の作成を手伝ってください。何をするか質問してください。",
|
||||
"vi": "Giúp tôi dựng một Schedule Task chạy hàng ngày. Hỏi tôi nó cần làm gì."},
|
||||
|
||||
"welcome.card_graph": {
|
||||
"en": "Ask GraphRAG", "ja": "GraphRAG に質問", "vi": "Hỏi GraphRAG"},
|
||||
"welcome.card_graph_sub": {
|
||||
"en": "Query this project's knowledge graph",
|
||||
"ja": "このプロジェクトの知識グラフを検索",
|
||||
"vi": "Truy vấn đồ thị tri thức của project"},
|
||||
"welcome.prompt_graph": {
|
||||
"en": "Using this project's knowledge graph, explain how the main pieces fit together.",
|
||||
"ja": "このプロジェクトの知識グラフを使って、主要な要素の関係を説明してください。",
|
||||
"vi": "Dùng đồ thị tri thức của project này, giải thích các phần chính ghép với nhau thế nào."},
|
||||
|
||||
"chatpanel.agent_tooltip": {
|
||||
"en": "Model/agent for THIS tab — independent of the other tab",
|
||||
"ja": "このタブ専用のモデル/エージェント(他のタブとは独立)",
|
||||
|
||||
@@ -27,6 +27,7 @@ from ...state import AppContext
|
||||
from ...theme import current_palette
|
||||
from .chat_bubble_style import ThinkingIndicator
|
||||
from .chat_history_widget import ChatView
|
||||
from .chat_welcome import ChatWelcome
|
||||
from .composer_widget import Composer
|
||||
from ...ui.icons import collapse_right_icon, icon as app_icon
|
||||
from ...ui.osutil import is_image, open_path
|
||||
@@ -43,7 +44,13 @@ class ChatPanelLayoutMixin:
|
||||
cc = QVBoxLayout(chat_col)
|
||||
cc.setContentsMargins(0, 0, 0, 0)
|
||||
cc.setSpacing(0)
|
||||
# Man gioi thieu chiem dung cho cua khung chat va thay the no khi hoi
|
||||
# thoai con rong — hai thu khong bao gio cung hien.
|
||||
self.welcome = ChatWelcome()
|
||||
self.welcome.suggestion_picked.connect(self._use_suggestion)
|
||||
cc.addWidget(self.welcome, 1)
|
||||
cc.addWidget(self.chat_view, 1)
|
||||
self.chat_view.hide() # phien moi thi rong -> man gioi thieu di truoc
|
||||
self.thinking = ThinkingIndicator() # animated "working…" line while we wait
|
||||
cc.addWidget(self.thinking)
|
||||
self.center_split = QSplitter(Qt.Horizontal)
|
||||
@@ -148,3 +155,61 @@ class ChatPanelLayoutMixin:
|
||||
self.center_split.setChildrenCollapsible(False)
|
||||
self.center_split.setSizes([820, 220])
|
||||
on_language_changed(self._retranslate_base)
|
||||
|
||||
# ---- man gioi thieu ----------------------------------------------------
|
||||
def _use_suggestion(self, text: str) -> None:
|
||||
"""Thẻ gợi ý được bấm: ĐIỀN vào ô nhập, không gửi luôn.
|
||||
|
||||
Câu gợi ý là điểm bắt đầu — người dùng gần như luôn cần thêm chi tiết
|
||||
của riêng họ, và gửi ngay sẽ tiêu một lượt gọi model cho một câu hỏi
|
||||
chung chung.
|
||||
"""
|
||||
self.composer.input.setPlainText(text)
|
||||
self.composer.input.setFocus()
|
||||
|
||||
def show_welcome(self, show: bool) -> None:
|
||||
"""Bật màn giới thiệu (hội thoại rỗng) hoặc khung chat (đã có tin)."""
|
||||
welcome = getattr(self, "welcome", None)
|
||||
if welcome is None:
|
||||
return
|
||||
welcome.setVisible(show)
|
||||
self.chat_view.setVisible(not show)
|
||||
if show:
|
||||
welcome.refresh(**self._welcome_context())
|
||||
|
||||
def _welcome_context(self) -> dict:
|
||||
"""Dữ liệu cho dòng bối cảnh. Không biết thì trả -1, KHÔNG trả 0.
|
||||
|
||||
Hiện "0 tệp" khi người dùng vừa nhìn thấy tệp trong thư mục còn tệ hơn
|
||||
là bỏ mảnh đó khỏi dòng meta.
|
||||
"""
|
||||
from pathlib import Path as _P
|
||||
|
||||
ten = ""
|
||||
try:
|
||||
from ...core.projects import load_project
|
||||
project = load_project(self.project_id) if getattr(self, "project_id", "") else None
|
||||
ten = project.name if project is not None else ""
|
||||
except Exception: # noqa: BLE001
|
||||
ten = ""
|
||||
|
||||
so_tep = -1
|
||||
try:
|
||||
folder = self.workspace_dir()
|
||||
if folder is not None and _P(folder).is_dir():
|
||||
so_tep = sum(1 for f in _P(folder).rglob("*")
|
||||
if f.is_file() and f.suffix.lower() in self._INPUT_EXTS)
|
||||
except Exception: # noqa: BLE001
|
||||
so_tep = -1
|
||||
|
||||
so_skill = -1
|
||||
try:
|
||||
from ...core.skills import list_skills
|
||||
so_skill = sum(1 for sk in list_skills() if getattr(sk, "enabled", False))
|
||||
except Exception: # noqa: BLE001
|
||||
so_skill = -1
|
||||
|
||||
# Ten nguoi dung do cua so chinh giu (app.py truyen xuong MainWindow).
|
||||
window = self.window()
|
||||
return {"user_name": getattr(window, "_user_name", "") or "",
|
||||
"project": ten, "files": so_tep, "skills": so_skill}
|
||||
|
||||
@@ -194,6 +194,8 @@ class ChatSessionMixin:
|
||||
"""
|
||||
from ...core.history import new_session_id
|
||||
|
||||
self.show_welcome(True) # hội thoại rỗng -> màn giới thiệu
|
||||
|
||||
# Allowed while work is running: current turns keep going in the background.
|
||||
self._detach_live_turns()
|
||||
self.messages = []
|
||||
@@ -230,6 +232,8 @@ class ChatSessionMixin:
|
||||
# turn must NOT tear down its live rendering — just no-op.
|
||||
if sid == self.session_id and self._view_busy():
|
||||
return
|
||||
# Hoi thoai da luu thi co tin nhan -> khung chat, khong phai man gioi thieu.
|
||||
self.show_welcome(not (conv.get("messages") or []))
|
||||
self._detach_live_turns()
|
||||
self.session_id = sid
|
||||
self.title = conv.get("title", "")
|
||||
|
||||
@@ -65,6 +65,10 @@ class ChatTurnRunnerMixin:
|
||||
attachments = attachments or []
|
||||
typed = text
|
||||
prefix, request, info = self._apply_skill_command(text)
|
||||
# Moi duong tra ve som duoi day cung them mot bong nguoi dung vao khung,
|
||||
# nen man gioi thieu phai nhuong cho ngay tai day — dat sau tung
|
||||
# add_user() thi de sot dung mot nhanh, va nhanh do se hien ca hai thu.
|
||||
self.show_welcome(False)
|
||||
if info is not None:
|
||||
# A local /skill command (list / select / error) — answer inline.
|
||||
self.chat_view.add_user(typed)
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
"""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()
|
||||
@@ -0,0 +1,143 @@
|
||||
"""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 — trạng thái duy nhất người dùng nhìn thấy trước khi
|
||||
họ gõ chữ đầu tiên.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("PySide6", reason="cần PySide6 để dựng widget thật")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def welcome(qapp):
|
||||
from cowork_local.presentation.chat.chat_welcome import ChatWelcome
|
||||
w = ChatWelcome()
|
||||
yield w
|
||||
w.deleteLater()
|
||||
|
||||
|
||||
# ---- bốn thẻ gợi ý -------------------------------------------------------
|
||||
|
||||
def test_co_dung_bon_the(welcome):
|
||||
assert len(welcome.cards) == 4
|
||||
|
||||
|
||||
def test_bam_the_thi_phat_cau_goi_y(welcome):
|
||||
nhan = []
|
||||
welcome.suggestion_picked.connect(nhan.append)
|
||||
|
||||
welcome.cards[0].click()
|
||||
|
||||
assert len(nhan) == 1
|
||||
assert len(nhan[0]) > 20, "câu gợi ý phải là một câu thật, không phải nhãn thẻ"
|
||||
|
||||
|
||||
def test_bon_the_cho_bon_cau_khac_nhau(welcome):
|
||||
nhan = []
|
||||
welcome.suggestion_picked.connect(nhan.append)
|
||||
|
||||
for card in welcome.cards:
|
||||
card.click()
|
||||
|
||||
assert len(set(nhan)) == 4
|
||||
|
||||
|
||||
def test_the_co_tieu_de_va_mo_ta(welcome):
|
||||
for card in welcome.cards:
|
||||
assert card.title_label.text().strip()
|
||||
assert card.sub_label.text().strip()
|
||||
# Nhãn của chính QPushButton phải rỗng — chữ do hai QLabel bên trong vẽ,
|
||||
# đặt cả hai chỗ sẽ in đè lên nhau.
|
||||
assert card.text() == ""
|
||||
|
||||
|
||||
# ---- dòng bối cảnh: KHÔNG BIẾT khác 0 ------------------------------------
|
||||
|
||||
def test_khong_biet_so_tep_thi_bo_manh_do(welcome):
|
||||
"""Hiện "0 tệp" khi người dùng vừa thấy có tệp trong thư mục còn tệ hơn là
|
||||
bỏ mảnh đó khỏi dòng meta."""
|
||||
welcome.refresh(user_name="local", project="p", files=-1, skills=-1)
|
||||
|
||||
meta = welcome.meta_label.text()
|
||||
assert "p" in meta
|
||||
assert "0" not in meta
|
||||
|
||||
|
||||
def test_khong_co_tep_that_thi_van_hien_so_0(welcome):
|
||||
"""Khác với KHÔNG BIẾT: thư mục rỗng thật thì nói rõ là rỗng."""
|
||||
welcome.refresh(user_name="local", project="p", files=0, skills=0)
|
||||
|
||||
assert "0" in welcome.meta_label.text()
|
||||
|
||||
|
||||
def test_khong_co_gi_de_noi_thi_an_dong_meta(welcome):
|
||||
welcome.refresh()
|
||||
|
||||
assert welcome.meta_label.isHidden() is True
|
||||
|
||||
|
||||
def test_co_ten_thi_chao_ten(welcome):
|
||||
welcome.refresh(user_name="Mynt4")
|
||||
|
||||
assert "Mynt4" in welcome.greeting_label.text()
|
||||
|
||||
|
||||
def test_khong_co_ten_thi_khong_chao_rong(welcome):
|
||||
"""Không được ra "Chào — bắt đầu từ đâu?" với một khoảng trống."""
|
||||
from cowork_local.i18n import tr
|
||||
|
||||
welcome.refresh(user_name="")
|
||||
|
||||
assert welcome.greeting_label.text() == tr("welcome.greeting_anon")
|
||||
|
||||
|
||||
# ---- i18n ----------------------------------------------------------------
|
||||
|
||||
@pytest.mark.parametrize("key", [
|
||||
"welcome.greeting", "welcome.greeting_anon", "welcome.meta_project",
|
||||
"welcome.meta_files", "welcome.meta_skills",
|
||||
"welcome.card_docs", "welcome.card_docs_sub", "welcome.prompt_docs",
|
||||
"welcome.card_data", "welcome.card_data_sub", "welcome.prompt_data",
|
||||
"welcome.card_schedule", "welcome.card_schedule_sub", "welcome.prompt_schedule",
|
||||
"welcome.card_graph", "welcome.card_graph_sub", "welcome.prompt_graph",
|
||||
])
|
||||
def test_key_co_du_ba_ngon_ngu(key):
|
||||
from cowork_local import i18n
|
||||
|
||||
for lang in ("en", "ja", "vi"):
|
||||
assert i18n.STRINGS[key].get(lang), f"{key} thiếu {lang}"
|
||||
|
||||
|
||||
def test_doi_ngon_ngu_thi_the_doi_theo(welcome):
|
||||
from cowork_local import i18n
|
||||
|
||||
truoc = i18n.get_language()
|
||||
try:
|
||||
i18n.set_language("vi")
|
||||
welcome.refresh(user_name="x", project="p")
|
||||
vi = welcome.cards[0].title_label.text()
|
||||
i18n.set_language("ja")
|
||||
ja = welcome.cards[0].title_label.text()
|
||||
finally:
|
||||
i18n.set_language(truoc)
|
||||
|
||||
assert vi != ja, "thẻ không được áp lại khi đổi ngôn ngữ"
|
||||
|
||||
|
||||
# ---- màu lấy từ theme ----------------------------------------------------
|
||||
|
||||
def test_duoc_style_bang_token_trong_theme():
|
||||
"""Guardrail G4: ngoài theme/ không file nào được đặt tên một màu."""
|
||||
from pathlib import Path
|
||||
from cowork_local.theme.qss import _TEMPLATE
|
||||
|
||||
for rule in ("QWidget#chatWelcome", "QPushButton#welcomeCard",
|
||||
"QLabel#welcomeGreeting"):
|
||||
assert rule in _TEMPLATE.template, f"{rule} chưa được style trong theme"
|
||||
src = (Path(__file__).resolve().parents[2]
|
||||
/ "presentation" / "chat" / "chat_welcome.py").read_text(encoding="utf-8")
|
||||
assert "setStyleSheet" not in src
|
||||
@@ -303,6 +303,21 @@ QPushButton#detailCopyBtn:hover { background: rgba(255,255,255,0.25); }
|
||||
QLabel { background: transparent; }
|
||||
QLabel#hint { color: $text_muted; }
|
||||
|
||||
/* Man gioi thieu cua khung chat (trang thai rong). */
|
||||
QWidget#chatWelcome { background: $bg; }
|
||||
QLabel#welcomeGreeting { color: $text; font-size: 19px; font-weight: 600; }
|
||||
QLabel#welcomeMark {
|
||||
background: $accent_soft; border: 1px solid $border_strong;
|
||||
border-radius: ${radius}px;
|
||||
}
|
||||
QPushButton#welcomeCard {
|
||||
background: $surface; border: 1px solid $border; border-radius: ${radius}px;
|
||||
text-align: left; padding: 0px;
|
||||
}
|
||||
QPushButton#welcomeCard:hover { background: $hover; border-color: $border_strong; }
|
||||
QPushButton#welcomeCard:focus { border: 1px solid $focus_ring; }
|
||||
QLabel#welcomeCardTitle { color: $text; font-weight: 600; }
|
||||
|
||||
/* Panel "dang tai" phu len khung do thi GraphRAG. Dung khung QWebEngineView la
|
||||
viec DONG BO tren GUI thread (~1-2s), nen thanh tien trinh o day khong chay
|
||||
duoc trong giai doan do — panel van phai nhin thay duoc, va do la ly do no co
|
||||
|
||||
Reference in New Issue
Block a user