diff --git a/app.py b/app.py index d913790..c8b8202 100644 --- a/app.py +++ b/app.py @@ -416,6 +416,21 @@ class MainWindow(QMainWindow): self._update_dock_guard() self.help_agent.reposition() self.help_agent.raise_() + # Build GraphRAG's browser view and first graph once the window is up + # and idle, so clicking GraphRAG does not sit on an empty view while + # both happen. 3s is after the first paint and any startup refresh. + if not getattr(self, "_graph_prewarmed", False): + self._graph_prewarmed = True + QTimer.singleShot(3000, self._prewarm_graph) + + def _prewarm_graph(self) -> None: + view = getattr(self, "structure", None) + if view is None or not hasattr(view, "prewarm"): + return + try: + view.prewarm() + except Exception: # noqa: BLE001 — a warm-up must never break the app + pass # ---- i18n ---------------------------------------------------------- def _retranslate(self) -> None: diff --git a/tools/check_graphrag_rescan.py b/tools/check_graphrag_rescan.py index c65038b..860a50c 100644 --- a/tools/check_graphrag_rescan.py +++ b/tools/check_graphrag_rescan.py @@ -50,24 +50,39 @@ def main() -> int: win.show() app.processEvents() st, w = win.structure, win.workspace - fails = [] + fails: list[str] = [] - # first visit builds the view and scans once - win._goto(win._ROW_WORKSPACE, w._graphrag_tab_idx) + # startup itself must not build any of it + if st.web is not None or st._graph is not None: + fails.append("dung san do thi ngay luc khoi dong — startup phai nhe") + + # the idle warm-up builds the browser view and the first graph off the + # click path (MainWindow fires this on a 3s timer; call it directly here) + win._prewarm_graph() deadline = time.monotonic() + 30 while st._graph is None and time.monotonic() < deadline: app.processEvents() time.sleep(0.02) if st._graph is None: - print("FAIL lan dau khong dung duoc do thi") + print("FAIL lam nong khong dung duoc do thi") sys.stdout.flush() os._exit(1) + print(f"sau khi lam nong: web={'co' if st.web else 'chua'} do thi={'co' if st._graph else 'chua'}") calls = {"scan": 0, "html": 0} real_scan, real_html = st._scan, st._render_d3 st._scan = lambda: (calls.__setitem__("scan", calls["scan"] + 1), real_scan())[1] st._render_d3 = lambda: (calls.__setitem__("html", calls["html"] + 1), real_html())[1] + # ...so the click itself does nothing but show it — this is the flash + t0 = time.monotonic() + win._goto(win._ROW_WORKSPACE, w._graphrag_tab_idx) + app.processEvents() + print(f"bam vao GraphRAG: {(time.monotonic() - t0) * 1000:.0f}ms " + f"_scan={calls['scan']} setHtml={calls['html']}") + if calls["scan"] or calls["html"]: + fails.append("bam vao GraphRAG van phai quet/nap lai trang — con chop trang") + # re-entering an unchanged graph must not rebuild anything for _ in range(3): win._goto(win._ROW_WORKSPACE, w._project_tab_idx) diff --git a/ui/structure_graph_view.py b/ui/structure_graph_view.py index eb7a4c6..b195bf0 100644 --- a/ui/structure_graph_view.py +++ b/ui/structure_graph_view.py @@ -495,10 +495,31 @@ class StructureGraphView(QWidget): f'
{html.escape(text)}
') + def prewarm(self) -> None: + """Pay for the graph view before it is clicked on, not during. + + Opening GraphRAG built a QWebEngineView (~140ms) and scanned the project + (~485ms) while an empty browser sat on screen — long enough, and white + enough, to read as the app restarting itself. Called from an idle timer + after the window is up, so startup itself is unaffected; the memory the + lazy construction was saving is spent a few seconds later instead. + """ + if not _HAS_WEB or self.web is not None: + return + self._ensure_web() + if self._graph is None and self.path_edit.text().strip(): + self._needs_scan = False + self._scan() # runs on a worker thread + def _ensure_web(self) -> None: if self.web is not None or not _HAS_WEB: return self.web = QWebEngineView() + # Blank the page in the app's own background first. A fresh + # QWebEngineView paints white, and on a dark theme that white rectangle + # WAS the flash — it showed for as long as the first scan took. + self.web.setHtml( + f"") self._bridge = _Bridge() self._channel = QWebChannel() self._channel.registerObject("py", self._bridge)