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:
@@ -44,6 +44,16 @@ __all__ = ["Co4ECanvas", "CO4E_MIME"]
|
||||
|
||||
|
||||
class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
"""Khung vẽ luồng Co4E: node là bước, cạnh là thứ tự chạy.
|
||||
|
||||
Lớp này giữ *dữ liệu đồ thị* (thêm/xoá node, nối cạnh, định tuyến đường
|
||||
nối). Phần thao tác chuột/bàn phím — phóng to, kéo màn, kéo-thả từ bảng
|
||||
nguyên liệu — nằm ở ``_CanvasInteractionMixin`` để file này không vượt
|
||||
hạn mức 400 dòng.
|
||||
|
||||
Mọi thay đổi làm đồ thị khác đi đều phát ``graph_changed`` để lớp trên
|
||||
tự lưu.
|
||||
"""
|
||||
node_selected = Signal(str) # a node was clicked (→ config panel)
|
||||
node_activated = Signal(str) # double-clicked
|
||||
graph_changed = Signal() # nodes/edges/positions changed (autosave)
|
||||
@@ -51,6 +61,7 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
_ZOOM_MIN, _ZOOM_MAX = 0.3, 3.0
|
||||
|
||||
def __init__(self):
|
||||
"""Khung vẽ workflow: kéo chọn theo vùng, thu phóng lấy con trỏ làm tâm."""
|
||||
super().__init__()
|
||||
self.setObjectName("co4eCanvas") # themed frame (see theme.py)
|
||||
self._scene = QGraphicsScene(self)
|
||||
@@ -73,6 +84,12 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
|
||||
# ---- load / serialize -------------------------------------------------
|
||||
def load(self, nodes, edges) -> None:
|
||||
"""Nạp lại toàn bộ đồ thị, xoá sạch khung cũ.
|
||||
|
||||
Cạnh trỏ tới node không tồn tại thì bỏ lặng lẽ — file luồng sửa tay
|
||||
hoặc luồng cũ có thể còn cạnh mồ côi, và treo cả màn vì một cạnh hỏng
|
||||
thì tệ hơn là bỏ nó đi.
|
||||
"""
|
||||
self._scene.clear()
|
||||
self._nodes.clear()
|
||||
self._edges.clear()
|
||||
@@ -89,14 +106,22 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
self._reposition_edges()
|
||||
|
||||
def nodes(self):
|
||||
"""Danh sách node (dạng dữ liệu, không phải item đồ hoạ) để đem đi lưu."""
|
||||
return [it.node for it in self._nodes.values()]
|
||||
|
||||
def edges(self):
|
||||
"""Danh sách cạnh (dạng dữ liệu) để đem đi lưu."""
|
||||
return [it.edge for it in self._edges]
|
||||
|
||||
# ---- mutation ---------------------------------------------------------
|
||||
def add_node(self, step: Step, x: float = 60.0, y: float = 60.0,
|
||||
connect_from: str = "") -> str:
|
||||
"""Thêm một bước vào khung và trả về id node vừa tạo.
|
||||
|
||||
``connect_from`` trỏ tới node nào thì nối luôn một cạnh từ đó sang; id
|
||||
không tồn tại thì bỏ qua phần nối. Node mới luôn được chọn ngay để bảng
|
||||
thuộc tính bên phải mở đúng bước vừa thêm.
|
||||
"""
|
||||
node = Node(id=new_node_id(), x=x, y=y, data=step)
|
||||
item = _NodeItem(node, self)
|
||||
self._nodes[node.id] = item
|
||||
@@ -123,13 +148,24 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
return tails[-1] if tails else (next(reversed(self._nodes), "") if self._nodes else "")
|
||||
|
||||
def add_palette_step(self, step: Step, pos: QPointF) -> None:
|
||||
"""Thả một bước từ bảng nguyên liệu xuống đúng vị trí con trỏ.
|
||||
|
||||
Tự nối vào đuôi chuỗi hiện có, để kéo liên tiếp vài bước là thành một
|
||||
luồng chạy được mà không phải nối tay từng cạnh.
|
||||
"""
|
||||
tail = self._chain_tail()
|
||||
self.add_node(step, x=pos.x(), y=pos.y(), connect_from=tail)
|
||||
|
||||
def begin_connect(self, source_id: str) -> None:
|
||||
"""Bắt đầu nối cạnh bằng menu chuột phải: ghi nhớ node nguồn."""
|
||||
self._connect_from = source_id
|
||||
|
||||
def _finish_connect(self, target_id: str) -> None:
|
||||
"""Kết thúc lượt nối bằng menu: tạo cạnh và xoá trạng thái đang nối.
|
||||
|
||||
Tự nối vào chính mình thì không tạo cạnh, nhưng vẫn xoá trạng thái —
|
||||
nếu không, lần bấm kế tiếp sẽ nối nhầm từ node cũ.
|
||||
"""
|
||||
src = self._connect_from
|
||||
self._connect_from = None
|
||||
if src and src != target_id:
|
||||
@@ -137,6 +173,7 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
|
||||
# ---- manual drag-to-connect (from a node's output port) ---------------
|
||||
def begin_port_drag(self, source_id: str, scene_pt: QPointF) -> None:
|
||||
"""Bắt đầu kéo cạnh từ cổng ra của một node: dựng đường nét đứt tạm."""
|
||||
self._port_src = source_id
|
||||
self._port_src_pt = scene_pt
|
||||
self._temp_edge = QGraphicsPathItem()
|
||||
@@ -146,11 +183,17 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
self._scene.addItem(self._temp_edge)
|
||||
|
||||
def update_port_drag(self, scene_pt: QPointF) -> None:
|
||||
"""Vẽ lại đường nét đứt theo con trỏ trong lúc kéo."""
|
||||
if self._temp_edge is None or self._port_src_pt is None:
|
||||
return
|
||||
self._temp_edge.setPath(_ortho_path(self._port_src_pt, scene_pt))
|
||||
|
||||
def finish_port_drag(self, scene_pt: QPointF) -> None:
|
||||
"""Thả chuột: nối cạnh nếu rơi trúng một node khác, và luôn dọn đường tạm.
|
||||
|
||||
Thả vào chỗ trống hay vào chính node nguồn đều không tạo cạnh — nhưng
|
||||
trạng thái kéo vẫn phải được xoá, nếu không đường nét đứt sẽ dính lại.
|
||||
"""
|
||||
src = self._port_src
|
||||
if self._temp_edge is not None:
|
||||
self._scene.removeItem(self._temp_edge)
|
||||
@@ -162,12 +205,18 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
self._make_edge(src, tgt)
|
||||
|
||||
def _node_at(self, scene_pt: QPointF) -> Optional[str]:
|
||||
"""Id node nằm dưới một điểm trên khung; ``None`` nếu là chỗ trống."""
|
||||
for it in self._scene.items(scene_pt):
|
||||
if isinstance(it, _NodeItem):
|
||||
return it.node.id
|
||||
return None
|
||||
|
||||
def _make_edge(self, source: str, target: str) -> None:
|
||||
"""Tạo cạnh nguồn → đích.
|
||||
|
||||
Bỏ qua cạnh tự nối và cạnh trùng cặp đã có — kéo hai lần cùng một
|
||||
hướng không được sinh ra hai đường chồng lên nhau.
|
||||
"""
|
||||
if source == target:
|
||||
return
|
||||
if any(e.edge.source == source and e.edge.target == target for e in self._edges):
|
||||
@@ -178,11 +227,15 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
self.graph_changed.emit()
|
||||
|
||||
def _add_edge_item(self, edge: Edge) -> None:
|
||||
"""Đưa một cạnh vào khung vẽ và vào danh sách quản lý."""
|
||||
item = _EdgeItem(edge, self)
|
||||
self._edges.append(item)
|
||||
self._scene.addItem(item)
|
||||
|
||||
def delete_edge(self, edge: Edge) -> None:
|
||||
"""Xoá cạnh, so khớp theo cặp nguồn/đích chứ không chỉ theo danh tính đối
|
||||
tượng — cạnh có thể đã được dựng lại sau một lần nạp.
|
||||
"""
|
||||
for e in list(self._edges):
|
||||
if e.edge is edge or (e.edge.source == edge.source and e.edge.target == edge.target):
|
||||
self._scene.removeItem(e)
|
||||
@@ -190,6 +243,7 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
self.graph_changed.emit()
|
||||
|
||||
def delete_node(self, node_id: str) -> None:
|
||||
"""Xoá một node và mọi cạnh dính vào nó. Id không tồn tại thì bỏ qua."""
|
||||
item = self._nodes.pop(node_id, None)
|
||||
if item is None:
|
||||
return
|
||||
@@ -202,23 +256,31 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
self.graph_changed.emit()
|
||||
|
||||
def delete_selected(self) -> None:
|
||||
"""Xoá mọi node và cạnh đang được chọn.
|
||||
|
||||
Xoá node trước: node kéo theo cạnh của nó, nên vòng lặp cạnh phía sau
|
||||
chỉ còn phải xử lý các cạnh được chọn riêng lẻ.
|
||||
"""
|
||||
for nid in [it.node.id for it in self._nodes.values() if it.isSelected()]:
|
||||
self.delete_node(nid)
|
||||
for e in [it.edge for it in self._edges if it.isSelected()]:
|
||||
self.delete_edge(e)
|
||||
|
||||
def update_node_status(self, node_id: str, status: str) -> None:
|
||||
"""Đổi màu trạng thái của một node lúc luồng đang chạy (đang chạy/xong/lỗi)."""
|
||||
item = self._nodes.get(node_id)
|
||||
if item is not None:
|
||||
item.status = status
|
||||
item.update()
|
||||
|
||||
def reset_statuses(self) -> None:
|
||||
"""Đưa mọi node về trạng thái chờ — gọi trước mỗi lần chạy lại luồng."""
|
||||
for it in self._nodes.values():
|
||||
it.status = "idle"
|
||||
it.update()
|
||||
|
||||
def refresh_node(self, node_id: str) -> None:
|
||||
"""Vẽ lại một node sau khi nội dung bước của nó bị sửa."""
|
||||
item = self._nodes.get(node_id)
|
||||
if item is not None:
|
||||
item.update()
|
||||
@@ -236,6 +298,11 @@ class Co4ECanvas(_CanvasInteractionMixin, QGraphicsView):
|
||||
return out
|
||||
|
||||
def _reposition_edges(self) -> None:
|
||||
"""Định tuyến lại mọi đường nối.
|
||||
|
||||
Cạnh luôn đi từ cổng ra (giữa cạnh phải) sang cổng vào (giữa cạnh trái)
|
||||
và lách qua các node khác, nên luồng đọc được từ trái sang phải.
|
||||
"""
|
||||
for e in self._edges:
|
||||
s = self._nodes.get(e.edge.source)
|
||||
t = self._nodes.get(e.edge.target)
|
||||
|
||||
Reference in New Issue
Block a user