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)