Files
cowork-local/presentation/graph/graph_export.py
T
f9f6bc01fd
CI / test (push) Canceled after 0s
Feature/delta team/epic r04 (#7)
## Summary

epic r04 - begin refactor

## Change Type

- [x] Cowork feature
- [ ] Bug fix
- [ ] Core AI contribution
- [ ] Test / hardening
- [ ] Performance
- [ ] Documentation

## Related Work

Cowork Task:

Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets

Core AI Issue:

Core Task:

Related PR:

## Scope

What is intentionally included?

What is intentionally NOT included?

## Validation

- [ ] Unit tests
- [ ] Integration tests
- [ ] Manual verification
- [ ] Regression check

Commands / evidence:

## Security Impact

Permission / credential / network / customer data impact:

## Compatibility

- [ ] No breaking change
- [ ] Breaking change documented

## Reviewer Notes

Anything Cowork reviewers should pay attention to.

---------

Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com>
Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com>
Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com>
Co-authored-by: Vu Dam Tuan <vudt15@fpt.com>
Co-authored-by: Hiep Ha Van <hiephv3@fpt.com>
Co-authored-by: Lam Hoang Van <lamhv7@fpt.com>
Reviewed-on: #7
Co-authored-by: Duy Le Huu <duylh19@fpt.com>
2026-08-31 05:15:13 +00:00

82 lines
3.4 KiB
Python

"""Xuất khung đồ thị đang xem ra file PNG — tách khỏi ``graph_renderer.py``.
GraphRAG có hai khung xem: cảnh Qt 2D và trang D3 chạy trong WebEngine. Hai
khung ấy chụp ảnh theo hai cách hoàn toàn khác nhau (``QWidget.grab()`` so
với một lượt gọi JavaScript trả về data URL), nên chỗ này gom cả hai lại sau
một hàm duy nhất và tự chọn đường đi.
"""
from __future__ import annotations
import base64
from typing import Callable, Optional
from PySide6.QtWidgets import QFileDialog, QWidget
from cowork_local.i18n import tr
#: Bên gọi truyền vào để hiện kết quả trên thanh trạng thái.
StatusFn = Callable[[str], None]
def ask_export_path(parent: QWidget) -> str:
"""Hỏi người dùng nơi lưu ảnh; trả về '' nếu họ bấm Huỷ."""
path, _ = QFileDialog.getSaveFileName(
parent, tr("structure.export_title"), "structure-graph.png", "PNG (*.png)")
return path or ""
def export_widget_grab(widget: QWidget, path: str, status: StatusFn) -> None:
"""Chụp thẳng widget đang hiện ra PNG.
Đây cũng là đường lui khi xuất từ D3 thất bại: chụp widget luôn cho ra
một tấm ảnh, dù không sắc nét bằng bản vẽ vector của D3.
"""
if widget.grab().save(path, "PNG"):
status(tr("structure.export_done", path=path))
else:
status(tr("structure.export_failed", err="grab() returned no image"))
def export_d3_png(web, path: str, fallback: QWidget, status: StatusFn) -> None:
"""Xuất PNG từ trang D3 bằng cách nhờ chính trang đó vẽ ra data URL.
``runJavaScript`` chạy bất đồng bộ nên kết quả về trong hàm gọi lại. Mọi
đường hỏng — trang chưa nạp xong, ``window.exportPng`` không tồn tại,
chuỗi trả về không phải data URL — đều quay sang chụp widget, để người
dùng bấm Xuất vẫn luôn nhận được một file thay vì im lặng không có gì.
"""
def on_result(data_url) -> None:
"""Nhận data URL từ trang D3 và ghi ra file; hỏng ở bất cứ đâu thì quay sang
chụp widget.
"""
if not isinstance(data_url, str) or "," not in data_url:
export_widget_grab(fallback, path, status)
return
try:
with open(path, "wb") as f:
f.write(base64.b64decode(data_url.split(",", 1)[1]))
status(tr("structure.export_done", path=path))
except (OSError, ValueError) as exc:
status(tr("structure.export_failed", err=str(exc)))
web.page().runJavaScript("window.exportPng ? window.exportPng() : ''", on_result)
def export_png(parent: QWidget, showing: QWidget, web: Optional[object],
is_web_visible: bool, status: StatusFn) -> None:
"""Hỏi đường dẫn rồi xuất khung ĐANG hiện — D3 hay cảnh Qt tuỳ tab đang mở.
Chỉ dùng đường D3 khi trang D3 thật sự đang hiển thị; đang xem cảnh Qt mà
lại chụp D3 thì ảnh ra không khớp với thứ người dùng nhìn thấy.
"""
path = ask_export_path(parent)
if not path:
return
if web is not None and is_web_visible:
export_d3_png(web, path, showing, status)
else:
export_widget_grab(showing, path, status)
__all__ = ["export_png", "export_d3_png", "export_widget_grab", "ask_export_path"]