feat(ui): GraphRAG giữ project đã chọn, tự quét, và báo khi đang tải
Cùng lớp lỗi với tab Thư mục: _bind_project gọi _structure.set_project(pid) vô điều kiện, và chuỗi _goto -> refresh -> _load_current -> _bind_project chạy lại mỗi lần vào lại màn Workspace, nên bộ chọn project của chính màn GraphRAG bị kéo về giá trị của Workspace ở mỗi cú chuyển tab. set_workspace_project() bỏ qua khi project không đổi (dùng None làm giá trị khởi tạo, không phải "", để lần gọi đầu không bị bỏ qua khi chưa chọn project nào). Chọn project xong phải tự quét: _on_project_changed cố ý hoãn lượt quét sang "lần ghé tiếp", nhưng combo nằm ngay trên màn đó nên người dùng đang đứng ở đấy và không có lần ghé tiếp nào — họ phải tự bấm Scan. Giờ quét ngay khi màn đang mở; hoãn vẫn giữ cho trường hợp đổi project từ màn Workspace khi GraphRAG ẩn. Panel "đang tải": prewarm() chỉ chạy 3 giây sau khi cửa sổ hiện, nên bấm GraphRAG trong 3 giây đầu gặp _ensure_web() dựng QWebEngineView ĐỒNG BỘ trên GUI thread — đóng băng 1-2 giây mà không có gì báo. Vì GUI thread bị chặn, panel phải repaint() ngay chứ không update(): update() chỉ xếp hàng một lượt vẽ cho vòng lặp sự kiện, mà vòng lặp đó sắp bị chặn, nên panel sẽ chỉ hiện ra sau khi hết đóng băng — đúng lúc không còn cần tới nó nữa. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,9 +9,11 @@ inline in ``ui/structure_graph_view.py::StructureGraphView.__init__`` (lines
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import QSplitter, QVBoxLayout, QWidget
|
||||
from PySide6.QtWidgets import (
|
||||
QHBoxLayout, QLabel, QProgressBar, QSplitter, QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
from cowork_local.i18n import on_language_changed
|
||||
from cowork_local.i18n import on_language_changed, tr
|
||||
from cowork_local.presentation.graph.graph_qa_widget import GraphQaWidget
|
||||
from cowork_local.presentation.graph.graph_renderer import GraphRenderer
|
||||
from cowork_local.state import AppContext
|
||||
@@ -35,6 +37,9 @@ class StructureGraphView(QWidget):
|
||||
"""
|
||||
super().__init__()
|
||||
self.ctx = ctx
|
||||
# Project ma man Workspace da ap xuong lan gan nhat. None = chua ap lan
|
||||
# nao, de lan goi dau tien khong bi bo qua ke ca khi pid la chuoi rong.
|
||||
self._workspace_project = None
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
self.renderer = GraphRenderer(ctx)
|
||||
@@ -50,6 +55,7 @@ class StructureGraphView(QWidget):
|
||||
self._split.setSizes(list(_COLLAPSED_SIZES_HINT))
|
||||
root.addWidget(self._split, 1)
|
||||
|
||||
self._build_busy_panel()
|
||||
on_language_changed(self._retranslate)
|
||||
|
||||
def _retranslate(self) -> None:
|
||||
@@ -72,18 +78,116 @@ class StructureGraphView(QWidget):
|
||||
self._split.setSizes(list(_COLLAPSED_SIZES_HINT))
|
||||
|
||||
# ---- public API (app.py / ui/workspace_tab.py) --------------------------- #
|
||||
# ---- panel "đang tải" ---------------------------------------------------
|
||||
def _build_busy_panel(self) -> None:
|
||||
"""Panel phủ lên khung đồ thị trong lúc nó đang được dựng.
|
||||
|
||||
Vì sao cần: ``prewarm()`` chỉ chạy 3 giây sau khi cửa sổ hiện
|
||||
(``main_window.py``), nên người dùng bấm GraphRAG trong 3 giây đầu sẽ
|
||||
gặp ``_ensure_web()`` dựng ``QWebEngineView`` ĐỒNG BỘ trên GUI thread —
|
||||
đóng băng 1-2 giây mà trước đây không có gì báo.
|
||||
"""
|
||||
self._busy = QWidget(self)
|
||||
self._busy.setObjectName("graphBusy")
|
||||
lay = QHBoxLayout(self._busy)
|
||||
lay.setContentsMargins(18, 14, 18, 14)
|
||||
lay.setSpacing(12)
|
||||
self._busy_label = QLabel()
|
||||
bar = QProgressBar()
|
||||
bar.setRange(0, 0) # chế độ vô định
|
||||
bar.setTextVisible(False)
|
||||
bar.setFixedWidth(120)
|
||||
lay.addWidget(self._busy_label)
|
||||
lay.addWidget(bar)
|
||||
self._busy.hide()
|
||||
self.renderer.graph_rendered.connect(self._hide_busy)
|
||||
# Đổi project ngay trên màn này thì renderer quét luôn, không chờ lần ghé
|
||||
# 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 _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():
|
||||
self._show_busy("structure.scanning")
|
||||
|
||||
def _show_busy(self, key: str) -> None:
|
||||
"""Hiện panel và ÉP VẼ NGAY.
|
||||
|
||||
``repaint()`` chứ không ``update()``: ``update()`` chỉ xếp hàng một lượt
|
||||
vẽ cho vòng lặp sự kiện, mà vòng lặp đó sắp bị chặn — panel sẽ chỉ hiện
|
||||
ra SAU khi hết đóng băng, đúng lúc không còn cần tới nó nữa.
|
||||
"""
|
||||
self._busy_label.setText(tr(key))
|
||||
self._busy.adjustSize()
|
||||
self._center_busy()
|
||||
self._busy.show()
|
||||
self._busy.raise_()
|
||||
self._busy.repaint()
|
||||
|
||||
def _hide_busy(self) -> None:
|
||||
"""Ẩn panel khi đồ thị đã vẽ xong."""
|
||||
if getattr(self, "_busy", None) is not None:
|
||||
self._busy.hide()
|
||||
|
||||
def _center_busy(self) -> None:
|
||||
"""Giữ panel ở giữa khung, kể cả khi cửa sổ đổi kích thước."""
|
||||
busy = getattr(self, "_busy", None)
|
||||
if busy is None:
|
||||
return
|
||||
size = busy.sizeHint()
|
||||
busy.setGeometry((self.width() - size.width()) // 2,
|
||||
(self.height() - size.height()) // 2,
|
||||
size.width(), size.height())
|
||||
|
||||
def resizeEvent(self, e): # noqa: N802
|
||||
"""Đổi kích thước cửa sổ thì panel phải theo."""
|
||||
super().resizeEvent(e)
|
||||
self._center_busy()
|
||||
|
||||
def schedule_rescan(self, path: str = "") -> None:
|
||||
"""Hẹn quét lại đồ thị sau khi thư mục có thay đổi."""
|
||||
self.renderer.schedule_rescan(path)
|
||||
|
||||
def auto_scan_and_fit(self) -> None:
|
||||
"""Vào màn GraphRAG: hiện đồ thị, chỉ quét lại khi thật sự cần."""
|
||||
self.renderer.auto_scan_and_fit()
|
||||
"""Vào màn GraphRAG: hiện đồ thị, chỉ quét lại khi thật sự cần.
|
||||
|
||||
Panel "đang tải" bật TRƯỚC khi gọi xuống renderer: phần dựng khung xem
|
||||
chặn GUI thread, nên phải vẽ panel ra trước lúc đó. Nó tự ẩn khi
|
||||
``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ề.
|
||||
"""
|
||||
da_dung_khung = self.renderer.web is not None
|
||||
self._show_busy("structure.scanning" if da_dung_khung else "structure.loading_view")
|
||||
try:
|
||||
self.renderer.auto_scan_and_fit()
|
||||
finally:
|
||||
# Khong co gi de ve (chua chon thu muc) thi graph_rendered khong phat
|
||||
# — panel se treo lai mai mai neu khong tu don o day.
|
||||
if not self.renderer.path_edit.text().strip():
|
||||
self._hide_busy()
|
||||
|
||||
def set_project(self, project_id: str) -> None:
|
||||
"""Khoá phạm vi quét vào một project (chuỗi rỗng là bỏ khoá)."""
|
||||
self.renderer.set_project(project_id)
|
||||
|
||||
def set_workspace_project(self, project_id: str) -> None:
|
||||
"""Áp project theo màn Workspace — bỏ qua nếu project KHÔNG đổi.
|
||||
|
||||
``WorkspaceTab._bind_project`` gọi xuống đây, và nó chạy lại mỗi lần
|
||||
người dùng vào lại màn Workspace (``_goto`` -> ``refresh`` ->
|
||||
``_load_current`` -> ``_bind_project``). Áp vô điều kiện thì bộ chọn
|
||||
project của chính màn GraphRAG bị kéo về giá trị của Workspace: chọn một
|
||||
project ở đây, sang tab khác rồi quay lại là mất.
|
||||
|
||||
Đổi sang project khác ở màn Workspace thì vẫn áp — cùng luật với tab Thư
|
||||
mục (``FolderTab.set_project_root``). Chỉ lần refresh trong CÙNG một
|
||||
project là không được đụng.
|
||||
"""
|
||||
if project_id == self._workspace_project:
|
||||
return
|
||||
self._workspace_project = project_id
|
||||
self.set_project(project_id)
|
||||
|
||||
def prewarm(self) -> None:
|
||||
"""Dựng sẵn khung đồ thị trước khi người dùng bấm vào, để lần mở đầu không giật."""
|
||||
self.renderer.prewarm()
|
||||
|
||||
Reference in New Issue
Block a user