From fd53c1cb4257422e749ad5436e6e53edcebae482 Mon Sep 17 00:00:00 2001 From: Anh Tran Nguyen Minh Date: Mon, 7 Sep 2026 19:55:43 +0900 Subject: [PATCH] =?UTF-8?q?fix(graph):=20v=C3=A0o=20m=C3=A0n=20GraphRAG=20?= =?UTF-8?q?t=E1=BB=B1=20=C4=91i=E1=BB=81n=20=C4=91=C6=B0=E1=BB=9Dng=20d?= =?UTF-8?q?=E1=BA=ABn=20qu=C3=A9t=20t=E1=BB=AB=20project=20=C4=91ang=20ch?= =?UTF-8?q?=E1=BB=8Dn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GraphRenderer.auto_scan_and_fit() thoát sớm khi ô đường dẫn rỗng, trong khi chính nút Scan lại có đường lùi (path_edit.text() or Path.cwd()). Bất đối xứng đó nghĩa là vào màn thì không làm gì, bấm Scan thì chạy. Cố ý KHÔNG lấy cwd() làm đường lùi như nút Scan — quét thư mục làm việc của tiến trình là quét một cây không liên quan gì tới project và có thể rất lớn. Điền từ project đang hoạt động, không có project thì vẫn không quét. LƯU Ý: chưa tái hiện được ca "lần đầu khởi động không tự nạp" mà người dùng báo. Probe với project và thư mục thật cho thấy cả hai đường (gọi thẳng auto_scan_and_fit và bấm GraphRAG trên thanh menu) đều khởi động lượt quét. Đây là bất đối xứng duy nhất tìm được; cần biết ô đường dẫn và combo project đang hiện gì lúc lỗi xảy ra để đi tiếp. Co-Authored-By: Claude Opus 5 (1M context) --- presentation/graph/structure_graph_view.py | 27 ++++++++++ tests/ui/test_graphrag_busy_panel.py | 60 ++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/presentation/graph/structure_graph_view.py b/presentation/graph/structure_graph_view.py index 9c5856d..8cc86f1 100644 --- a/presentation/graph/structure_graph_view.py +++ b/presentation/graph/structure_graph_view.py @@ -105,6 +105,32 @@ class StructureGraphView(QWidget): # sau — nên panel phải bật theo, không thì lượt quét đó lại im lặng. self.renderer.project_changed.connect(self._on_project_scan_started) + def _ensure_scan_path(self) -> None: + """Điền ô đường dẫn từ project đang hoạt động nếu nó đang rỗng. + + ``GraphRenderer.auto_scan_and_fit`` thoát sớm khi ô đường dẫn rỗng, + trong khi chính nút Scan lại có đường lùi (``path_edit.text() or + Path.cwd()``). Bất đối xứng đó nghĩa là: vào màn thì không làm gì, bấm + Scan thì chạy — đúng thứ người dùng phàn nàn. + + Điền từ project đang chọn chứ KHÔNG lấy ``cwd()`` làm đường lùi như nút + Scan: quét thư mục làm việc của tiến trình là quét một cây không liên + quan gì tới project, và nó có thể rất lớn. + """ + if self.renderer.path_edit.text().strip(): + return + ctx = getattr(self, "ctx", None) + pid = (getattr(ctx, "active_project_id", "") or "").strip() + if not pid or pid == "default": + return + try: + from ...core.projects import load_project + project = load_project(pid) + except Exception: # noqa: BLE001 + return + if project is not None: + self.renderer.path_edit.setText(str(project.workspace_dir())) + def _on_project_scan_started(self) -> None: """Renderer vừa đổi project. Nó chỉ quét ngay khi màn này đang mở.""" if self.renderer.isVisible(): @@ -156,6 +182,7 @@ class StructureGraphView(QWidget): ``graph_rendered`` phát — bao trọn cả lượt quét chạy ở luồng nền phía sau, chứ không tắt ngay khi hàm này trả về. """ + self._ensure_scan_path() da_dung_khung = self.renderer.web is not None self._show_busy("structure.scanning" if da_dung_khung else "structure.loading_view") try: diff --git a/tests/ui/test_graphrag_busy_panel.py b/tests/ui/test_graphrag_busy_panel.py index 234a39e..8dc308c 100644 --- a/tests/ui/test_graphrag_busy_panel.py +++ b/tests/ui/test_graphrag_busy_panel.py @@ -55,6 +55,7 @@ def _dung(qapp, ViewCls, web=None, path="C:/tmp"): v.renderer = _Renderer(qapp, web=web, path=path) v.renderer._view = v v._workspace_project = None + v.ctx = type("C", (), {"active_project_id": "default"})() v.resize(800, 600) v._build_busy_panel() return v @@ -199,3 +200,62 @@ def test_doi_project_khi_man_dang_an_thi_khong_bat_panel(qapp, view): assert v._busy.isHidden() is True finally: v.deleteLater() + + +# ---- đường dẫn quét rỗng thì lấy từ project đang hoạt động --------------- + +def test_o_duong_dan_rong_thi_dien_tu_project_dang_chon(qapp, view, monkeypatch, tmp_path): + """GraphRenderer.auto_scan_and_fit thoát sớm khi ô đường dẫn rỗng, trong khi + nút Scan lại có đường lùi — vào màn không làm gì, bấm Scan thì chạy.""" + import cowork_local.core.projects as projects + + ws = tmp_path / "ws" + ws.mkdir() + + class _P: + project_id = "p1" + def workspace_dir(self, base=None): + return ws + + monkeypatch.setattr(projects, "load_project", lambda pid: _P() if pid == "p1" else None) + + v = _dung(qapp, view, path="") + v.ctx = type("C", (), {"active_project_id": "p1"})() + v.renderer.path_edit = type("E", (), { + "_t": "", + "text": lambda s: s._t, + "setText": lambda s, t: setattr(s, "_t", t)})() + try: + v._ensure_scan_path() + + assert v.renderer.path_edit.text() == str(ws) + finally: + v.deleteLater() + + +def test_da_co_duong_dan_thi_khong_ghi_de(qapp, view): + v = _dung(qapp, view, path="C:/da-chon") + v.ctx = type("C", (), {"active_project_id": "p1"})() + try: + v._ensure_scan_path() + + assert v.renderer.path_edit.text() == "C:/da-chon" + finally: + v.deleteLater() + + +def test_chua_chon_project_thi_khong_bia_duong_dan(qapp, view): + """Không lấy cwd() làm đường lùi: quét thư mục làm việc của tiến trình là + quét một cây không liên quan gì tới project.""" + v = _dung(qapp, view, path="") + v.ctx = type("C", (), {"active_project_id": "default"})() + v.renderer.path_edit = type("E", (), { + "_t": "", + "text": lambda s: s._t, + "setText": lambda s, t: setattr(s, "_t", t)})() + try: + v._ensure_scan_path() + + assert v.renderer.path_edit.text() == "" + finally: + v.deleteLater()