Feature/fsg gamma team ui fix #3

Merged
gitea-admin merged 41 commits from feature/FSG_GammaTeam_UI_Fix into main 2026-08-20 12:12:59 +00:00
3 changed files with 55 additions and 4 deletions
Showing only changes of commit c4e9158779 - Show all commits
+15
View File
@@ -416,6 +416,21 @@ class MainWindow(QMainWindow):
self._update_dock_guard() self._update_dock_guard()
self.help_agent.reposition() self.help_agent.reposition()
self.help_agent.raise_() 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 ---------------------------------------------------------- # ---- i18n ----------------------------------------------------------
def _retranslate(self) -> None: def _retranslate(self) -> None:
+19 -4
View File
@@ -50,24 +50,39 @@ def main() -> int:
win.show() win.show()
app.processEvents() app.processEvents()
st, w = win.structure, win.workspace st, w = win.structure, win.workspace
fails = [] fails: list[str] = []
# first visit builds the view and scans once # startup itself must not build any of it
win._goto(win._ROW_WORKSPACE, w._graphrag_tab_idx) 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 deadline = time.monotonic() + 30
while st._graph is None and time.monotonic() < deadline: while st._graph is None and time.monotonic() < deadline:
app.processEvents() app.processEvents()
time.sleep(0.02) time.sleep(0.02)
if st._graph is None: 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() sys.stdout.flush()
os._exit(1) 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} calls = {"scan": 0, "html": 0}
real_scan, real_html = st._scan, st._render_d3 real_scan, real_html = st._scan, st._render_d3
st._scan = lambda: (calls.__setitem__("scan", calls["scan"] + 1), real_scan())[1] 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] 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 # re-entering an unchanged graph must not rebuild anything
for _ in range(3): for _ in range(3):
win._goto(win._ROW_WORKSPACE, w._project_tab_idx) win._goto(win._ROW_WORKSPACE, w._project_tab_idx)
+21
View File
@@ -495,10 +495,31 @@ class StructureGraphView(QWidget):
f'<pre style="white-space:pre-wrap; font-family:Consolas,monospace; ' f'<pre style="white-space:pre-wrap; font-family:Consolas,monospace; '
f'font-size:12px;">{html.escape(text)}</pre>') f'font-size:12px;">{html.escape(text)}</pre>')
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: def _ensure_web(self) -> None:
if self.web is not None or not _HAS_WEB: if self.web is not None or not _HAS_WEB:
return return
self.web = QWebEngineView() 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"<body style='margin:0;background:{current_palette().bg}'></body>")
self._bridge = _Bridge() self._bridge = _Bridge()
self._channel = QWebChannel() self._channel = QWebChannel()
self._channel.registerObject("py", self._bridge) self._channel.registerObject("py", self._bridge)