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:
@@ -36,6 +36,11 @@ class AiEditPipeline:
|
||||
"""
|
||||
|
||||
def __init__(self, owner) -> None:
|
||||
"""Vòng chạy một lượt sửa tệp bằng AI.
|
||||
|
||||
``pending`` giữ nội dung model đề xuất cho tới khi người dùng đồng ý: không
|
||||
bao giờ ghi đè tệp trước khi có xác nhận.
|
||||
"""
|
||||
self._owner = owner
|
||||
self.worker: Optional[AgentWorker] = None
|
||||
self.pending: Optional[dict] = None # proposed content awaiting confirmation
|
||||
@@ -72,6 +77,7 @@ class AiEditPipeline:
|
||||
|
||||
# ---- usage accounting (like Cowork's per-message footer) --------------- #
|
||||
def _add_usage(self, usage) -> None:
|
||||
"""Cộng dồn token của một lượt gọi vào tổng của cả phiên sửa file."""
|
||||
if not isinstance(usage, dict):
|
||||
return
|
||||
tot = self._prompt_usage
|
||||
@@ -81,6 +87,7 @@ class AiEditPipeline:
|
||||
tot["cost"] += float(usage.get("cost_usd", 0.0) or 0.0)
|
||||
|
||||
def _show_usage(self, bubble) -> None:
|
||||
"""Gắn dòng token/chi phí dưới bong bóng trả lời; chưa dùng token nào thì bỏ qua."""
|
||||
tot = self._prompt_usage
|
||||
if bubble is None or not (tot["in"] or tot["out"]):
|
||||
return
|
||||
@@ -96,12 +103,14 @@ class AiEditPipeline:
|
||||
|
||||
# ---- phase 1: plan ------------------------------------------------------- #
|
||||
def _run_plan(self) -> None:
|
||||
"""Bước 1 — nhờ model lập kế hoạch sửa trước khi động vào nội dung."""
|
||||
o = self._owner
|
||||
c = self._ctx
|
||||
plan_bubble = o.ai_chat.add_plan(tr("folder.ai_planning"))
|
||||
o.ai_chat.scroll_to_bottom()
|
||||
|
||||
def job(worker):
|
||||
"""Chạy nền: gọi model sinh kế hoạch và đo token đã dùng."""
|
||||
from cowork_local.core import usage_tracker as ut
|
||||
from cowork_local.core.co4e_runner import _usage_delta
|
||||
provider = c["provider"]
|
||||
@@ -131,6 +140,7 @@ class AiEditPipeline:
|
||||
worker.start()
|
||||
|
||||
def _plan_done(self, result, plan_bubble) -> None:
|
||||
"""Có kế hoạch: hiện ra rồi chuyển sang bước sửa thật."""
|
||||
self._add_usage((result or {}).get("usage"))
|
||||
plan = ((result or {}).get("plan") or "").strip()
|
||||
self._ctx["plan"] = plan
|
||||
@@ -140,6 +150,7 @@ class AiEditPipeline:
|
||||
|
||||
# ---- phase 2: execute (edit the file) ------------------------------------ #
|
||||
def _run_edit(self) -> None:
|
||||
"""Bước 2 — sinh nội dung mới, phát dần vào bong bóng trả lời."""
|
||||
o = self._owner
|
||||
c = self._ctx
|
||||
bubble = o.ai_chat.add_assistant(tr("folder.ai_edit"))
|
||||
@@ -178,6 +189,7 @@ class AiEditPipeline:
|
||||
pass
|
||||
|
||||
def job(worker):
|
||||
"""Chạy nền: gọi model sinh nội dung sửa, kèm ngữ cảnh tệp đang mở."""
|
||||
provider = c["provider"]
|
||||
open_note = (f"the currently-open file '{c['filename']}'" if c["filename"]
|
||||
else "no file is open")
|
||||
@@ -199,6 +211,7 @@ class AiEditPipeline:
|
||||
messages.append({"role": "user", "content": cur + f"Request: {c['instruction']}"})
|
||||
|
||||
def on_text(piece: str) -> None:
|
||||
"""Đẩy từng mẩu nội dung ra ngoài để giao diện vẽ dần."""
|
||||
worker.emit_event({"type": "text", "delta": piece})
|
||||
|
||||
from cowork_local.core import usage_tracker as ut
|
||||
@@ -221,11 +234,17 @@ class AiEditPipeline:
|
||||
worker.start()
|
||||
|
||||
def _stream(self, ev, bubble) -> None:
|
||||
"""Vẽ từng mẩu nội dung đang phát dần và cuộn theo."""
|
||||
if isinstance(ev, dict) and ev.get("type") == "text":
|
||||
bubble.append_delta(ev.get("delta", ""))
|
||||
self._owner.ai_chat.scroll_to_bottom()
|
||||
|
||||
def _done(self, result, bubble) -> None:
|
||||
"""Sinh xong: tách phần mã khỏi phần giải thích và dựng bản xem trước.
|
||||
|
||||
KHÔNG bao giờ ghi thẳng ra tệp ở đây — chỉ ``_finalize_apply`` mới chạm đĩa,
|
||||
và chỉ sau khi người dùng bấm Áp dụng.
|
||||
"""
|
||||
o = self._owner
|
||||
self.worker = None
|
||||
o.set_busy(False)
|
||||
@@ -281,6 +300,10 @@ class AiEditPipeline:
|
||||
self._finalize_apply(p)
|
||||
|
||||
def _generate_then_finalize(self, p: dict) -> None:
|
||||
"""Lượt sửa có yêu cầu sinh ảnh: tạo ảnh trước rồi mới ghi nội dung.
|
||||
|
||||
Phải theo thứ tự đó vì nội dung có thể tham chiếu tới đường dẫn ảnh vừa tạo.
|
||||
"""
|
||||
o = self._owner
|
||||
imgs = p.get("image_gens") or []
|
||||
root = os.path.normpath(o.preview.root)
|
||||
@@ -289,6 +312,7 @@ class AiEditPipeline:
|
||||
o.status_message.emit(tr("folder.ai_generating"))
|
||||
|
||||
def job(worker):
|
||||
"""Chạy nền: sinh lần lượt từng ảnh được yêu cầu."""
|
||||
from cowork_local.core import image_gen
|
||||
results = []
|
||||
for prompt, rel in imgs:
|
||||
@@ -314,6 +338,7 @@ class AiEditPipeline:
|
||||
worker.start()
|
||||
|
||||
def _images_done(self, res: dict, p: dict) -> None:
|
||||
"""Ảnh đã sinh xong: ghi nội dung ra đĩa."""
|
||||
o = self._owner
|
||||
self.worker = None
|
||||
o.set_busy(False)
|
||||
@@ -329,6 +354,7 @@ class AiEditPipeline:
|
||||
o.preview.open_file(created[0], reset_ai=False)
|
||||
|
||||
def _finalize_apply(self, p: dict, images_done: bool = False) -> None:
|
||||
"""Ghi nội dung đã được duyệt xuống đĩa và làm mới khung xem."""
|
||||
o = self._owner
|
||||
content = p.get("content")
|
||||
target = p.get("target")
|
||||
@@ -352,6 +378,7 @@ class AiEditPipeline:
|
||||
o.flag_done()
|
||||
|
||||
def discard(self) -> None:
|
||||
"""Bỏ bản đề xuất đang chờ, không chạm vào tệp."""
|
||||
o = self._owner
|
||||
self.pending = None
|
||||
o.show_confirm_row(False)
|
||||
@@ -361,6 +388,7 @@ class AiEditPipeline:
|
||||
o.maybe_dequeue() # discarding resolves the gate → run the next queued edit
|
||||
|
||||
def _failed(self, err, bubble) -> None:
|
||||
"""Lượt sửa lỗi: hiện lỗi trong bong bóng và mở khoá lại panel."""
|
||||
o = self._owner
|
||||
self.worker = None
|
||||
bubble.set_markdown(tr("folder.ai_error", err=err))
|
||||
|
||||
Reference in New Issue
Block a user