refactor: vá 4 hồi quy, tách 4 file chạm trần LOC, docstring lên 100%
Hồi quy đã vá
-------------
F-12 Kéo–thả hoặc dán tệp vào ô chat ném NameError. R08 tách `_Input` sang
`chat_input_box.py` nhưng để `_paths_from_mime()` ở lại
`composer_widget.py`, nên hai hàm sự kiện Qt gọi một cái tên không tồn
tại. Bốn hàm dùng chung chuyển sang `composer_mime.py` — module thứ ba
là chỗ duy nhất không lặp lại được lỗi này. Đo lại: cả thả lẫn dán đều
gắn 1 tệp, khớp bản trước refactor.
F-01 Đổi provider thì bộ chọn model AI-Edit không làm gì. Hook cũ kiểm
`folder.ai_model_combo`, thuộc tính R08-T12 đã dời sang
`ai_panel.resolver`. Làm mới vô điều kiện, đúng như tab cũ: lần lấy đầu
tiên hỏng thì đổi provider chính là lúc phải thử lại.
F-07 Hàng chọn kỳ của Dashboard bị đẩy xuống dưới các thẻ số liệu. Hàng này
lọc CẢ BA thẻ con chứ không riêng biểu đồ, nên để nó nằm dưới là bắt
người dùng đọc con số trước khi thấy con số đó tính cho kỳ nào. Kèm
theo: `TokenUsageCardWidget` bị bỏ sót `setContentsMargins(0,0,0,0)`
mà hai thẻ con còn lại đã có, đẩy cả hàng thẻ lệch 9px.
`check_layout_geometry` nay khớp TỪNG BYTE với bản trước refactor.
F-11 Hai lớp khai trùng tên phương thức; Python giữ bản sau nên bản đầu là
mã chết. `co4e_tab.py::showEvent` bản đầu gọi `_narrow_guard.attach()`
và không bao giờ chạy.
Tách file (F-09)
----------------
Bốn file chạm trần 400 dòng, mỗi lần cắt ra một trách nhiệm thật:
graph_renderer.py -> graph_scene_builder.py + graph_export.py
co4e_workflow_service.py -> co4e_run_history.py
json_config_repository.py -> config_sections.py
agents_admin_tab.py -> shared/agent_kind_visuals.py
File cuối còn xoá 3 bản sao của hàm đã có trong `shared/formatters.py`,
giống hệt đến từng dòng — nay định dạng thời gian và avatar không lệch nhau
giữa các bảng Giám sát nữa.
Docstring
---------
41,6% -> 100% (3.478/3.478 định nghĩa production), kể cả module dormant và
phương thức dunder. Toàn bộ phần bổ sung viết bằng tiếng Việt; comment tiếng
Anh có sẵn giữ nguyên — dịch ngược là một đợt riêng.
Seam chưa nối dây (F-05)
------------------------
9 seam mang nhãn `SEAM · dựng <ngày>` kèm hai câu: được nối khi nào, và để
dormant thì hỏng gì. Ngày lấy từ lịch sử git, không phải hạn tự đặt. Gate O
đọc nhãn đó và nhắc khi quá 30 ngày.
859 test xanh · 4/4 cổng CASAN · 19/24 checker khớp từng byte bản cũ.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,11 @@ class MessageBubble(QFrame):
|
||||
|
||||
def __init__(self, role: str, title: str = "", collapsible: bool = False,
|
||||
collapsed: bool = True):
|
||||
"""Một bong bóng tin nhắn.
|
||||
|
||||
``collapsible`` dành cho khối dài (kết quả tool, suy nghĩ của model): chúng
|
||||
mở sẵn thì đẩy phần trả lời thật ra khỏi màn hình.
|
||||
"""
|
||||
super().__init__()
|
||||
self.role = role
|
||||
self._text = ""
|
||||
@@ -92,12 +97,16 @@ class MessageBubble(QFrame):
|
||||
self._update_head()
|
||||
|
||||
def _toggle_body(self) -> None:
|
||||
"""Gập/mở phần thân bong bóng khi bấm vào dòng tiêu đề."""
|
||||
self.body.setVisible(not self.body.isVisible())
|
||||
if self.body.isVisible():
|
||||
self._autosize()
|
||||
self._update_head()
|
||||
|
||||
def _update_head(self) -> None:
|
||||
"""Vẽ lại dòng tiêu đề: mũi tên gập/mở, và khi đang gập thì kèm 70 ký tự
|
||||
đầu làm xem trước — để nhìn dòng tiêu đề là đoán được bên trong có gì.
|
||||
"""
|
||||
if not self._head:
|
||||
return
|
||||
expanded = self.body.isVisible()
|
||||
@@ -153,10 +162,12 @@ class MessageBubble(QFrame):
|
||||
return None
|
||||
|
||||
def append_delta(self, delta: str) -> None:
|
||||
"""Nối thêm một mẩu văn bản đang phát dần từ model rồi vẽ lại dạng markdown."""
|
||||
self._text += delta
|
||||
self.set_markdown(self._text)
|
||||
|
||||
def set_markdown(self, text: str) -> None:
|
||||
"""Đặt toàn bộ nội dung, hiển thị dạng markdown, rồi co giãn lại chiều cao."""
|
||||
self._text = text
|
||||
self.body.setMarkdown(text)
|
||||
self._autosize()
|
||||
@@ -164,6 +175,7 @@ class MessageBubble(QFrame):
|
||||
self._update_head()
|
||||
|
||||
def set_plain(self, text: str) -> None:
|
||||
"""Đặt toàn bộ nội dung dạng văn bản thuần (không diễn giải markdown)."""
|
||||
self._text = text
|
||||
self.body.setPlainText(text)
|
||||
self._autosize()
|
||||
@@ -171,6 +183,7 @@ class MessageBubble(QFrame):
|
||||
self._update_head()
|
||||
|
||||
def append_plain(self, delta: str) -> None:
|
||||
"""Nối thêm một mẩu văn bản thuần đang phát dần (dùng cho log tool)."""
|
||||
self._text += delta
|
||||
self.set_plain(self._text)
|
||||
|
||||
@@ -197,12 +210,14 @@ class MessageBubble(QFrame):
|
||||
self._content_layout.addWidget(lbl)
|
||||
|
||||
def add_delete_link(self, callback) -> None:
|
||||
"""Gắn liên kết "xoá" màu cảnh báo dưới bong bóng, bấm vào thì gọi ``callback``."""
|
||||
link = QLabel(f'<a href="#del" style="color:{_p().danger};">{tr("chat.delete_link")}</a>')
|
||||
link.setToolTip(tr("chat.delete_tooltip"))
|
||||
link.linkActivated.connect(lambda *_: callback())
|
||||
self._content_layout.addWidget(link)
|
||||
|
||||
def add_folder_link(self, folder: str, label: str | None = None) -> None:
|
||||
"""Gắn liên kết mở thư mục kết quả trong trình quản lý tệp của hệ điều hành."""
|
||||
label = label or tr("chat.open_workspace")
|
||||
link = QLabel(f'<a href="#open" style="color:{_p().accent};">{label}</a>')
|
||||
link.setToolTip(str(folder))
|
||||
@@ -229,6 +244,12 @@ class MessageBubble(QFrame):
|
||||
self._content_layout.addWidget(file_link)
|
||||
|
||||
def _autosize(self) -> None:
|
||||
"""Đặt chiều cao bong bóng vừa đúng nội dung.
|
||||
|
||||
QTextBrowser không tự co giãn theo nội dung, nên phải tính tay. Chặn
|
||||
trong khoảng 28..1200px: quá thấp thì mất chữ, quá cao thì một câu trả
|
||||
lời dài chiếm trọn màn hình và không cuộn tới cái sau được.
|
||||
"""
|
||||
width = self.body.viewport().width()
|
||||
if width <= 0:
|
||||
width = 560 # sensible default before the widget is laid out
|
||||
@@ -237,6 +258,7 @@ class MessageBubble(QFrame):
|
||||
self.body.setFixedHeight(max(28, min(height, 1200)))
|
||||
|
||||
def resizeEvent(self, event): # noqa: N802 - re-flow on width change
|
||||
"""Đổi bề rộng thì phải tính lại chiều cao — chữ xuống dòng khác đi."""
|
||||
super().resizeEvent(event)
|
||||
self._autosize()
|
||||
|
||||
@@ -249,6 +271,11 @@ class ChatView(QScrollArea):
|
||||
app switches between light and dark modes."""
|
||||
|
||||
def __init__(self):
|
||||
"""Vùng cuộn chứa dòng thời gian tin nhắn.
|
||||
|
||||
Có một ``addStretch`` ở cuối để khi ít tin, chúng dồn lên trên thay vì trôi
|
||||
giữa khung.
|
||||
"""
|
||||
super().__init__()
|
||||
self.setWidgetResizable(True)
|
||||
self._container = QWidget()
|
||||
@@ -269,21 +296,29 @@ class ChatView(QScrollArea):
|
||||
w.apply_theme()
|
||||
|
||||
def _add(self, bubble: MessageBubble) -> MessageBubble:
|
||||
"""Chèn bong bóng vào cuối danh sách (trước phần đệm co giãn) rồi cuộn xuống."""
|
||||
# insert before the trailing stretch
|
||||
self._lay.insertWidget(self._lay.count() - 1, bubble)
|
||||
self._scroll_to_bottom()
|
||||
return bubble
|
||||
|
||||
def add_user(self, text: str) -> MessageBubble:
|
||||
"""Thêm một tin nhắn của người dùng."""
|
||||
b = MessageBubble("user", tr("chat.you"))
|
||||
b.set_plain(text)
|
||||
return self._add(b)
|
||||
|
||||
def add_assistant(self, title: str | None = None) -> MessageBubble:
|
||||
"""Thêm một bong bóng trả lời rỗng để model phát nội dung dần vào."""
|
||||
b = MessageBubble("assistant", title or tr("chat.assistant"))
|
||||
return self._add(b)
|
||||
|
||||
def add_tool(self, title: str, body: str, ok: bool = True) -> MessageBubble:
|
||||
"""Thêm một bước tool (chạy lệnh, sinh mã, kết quả).
|
||||
|
||||
Chạy xuôi thì gập lại cho dòng thời gian ngắn gọn; lỗi thì mở sẵn vì đó
|
||||
là thứ người dùng cần đọc ngay.
|
||||
"""
|
||||
# Tool steps (run command, generated code/diff, output) are collapsible to
|
||||
# keep the transcript short — collapsed when OK, expanded on error.
|
||||
b = MessageBubble("tool" if ok else "error", title, collapsible=True, collapsed=ok)
|
||||
@@ -307,12 +342,17 @@ class ChatView(QScrollArea):
|
||||
return self._add(b)
|
||||
|
||||
def add_reasoning(self, title: str | None = None) -> MessageBubble:
|
||||
"""Thêm hộp suy luận nội bộ của model — gập sẵn, mở ra đọc được.
|
||||
|
||||
Để người dùng thấy nó đang nghĩ mà không bị lấp kín khung chat.
|
||||
"""
|
||||
# The model's private reasoning — a collapsed, collapsible box so the user
|
||||
# can see it's thinking (and expand to read) without it flooding the chat.
|
||||
b = MessageBubble("tool", title or tr("chat.thinking"), collapsible=True, collapsed=True)
|
||||
return self._add(b)
|
||||
|
||||
def add_error(self, text: str) -> MessageBubble:
|
||||
"""Thêm một bong bóng báo lỗi."""
|
||||
b = MessageBubble("error", tr("chat.error"))
|
||||
b.set_plain(text)
|
||||
return self._add(b)
|
||||
@@ -331,6 +371,7 @@ class ChatView(QScrollArea):
|
||||
return self._add(b)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Xoá sạch dòng thời gian, giữ lại phần đệm co giãn ở cuối."""
|
||||
while self._lay.count() > 1:
|
||||
item = self._lay.takeAt(0)
|
||||
w = item.widget()
|
||||
@@ -344,6 +385,7 @@ class ChatView(QScrollArea):
|
||||
QTimer.singleShot(80, self._scroll_to_bottom)
|
||||
|
||||
def _scroll_to_bottom(self) -> None:
|
||||
"""Kéo thanh cuộn xuống đáy ngay lập tức."""
|
||||
bar = self.verticalScrollBar()
|
||||
bar.setValue(bar.maximum())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user