feat(web): incremental render-window batch rendering engine
Add render_window.js: Python-driven incremental pagination with id+chainIndex state, anchor restore, batch guard and streaming auto-detect; wired through chat_bridge and the page shell.
This commit is contained in:
@@ -9,6 +9,9 @@ class ChatBridge(QObject):
|
||||
delete_message_requested = pyqtSignal(str) # 删除消息信号 (msg_id)
|
||||
attachment_clicked = pyqtSignal(str)
|
||||
scroll_changed = pyqtSignal(float, float, float) # 页面 scrollY / scrollHeight / innerHeight
|
||||
# 🆕 P1-01 渲染窗口:前端请求换页(fire-and-forget;响应经 run_js 推送)
|
||||
window_page_requested = pyqtSignal(str, str, str, int) # (session_id, direction, boundary_id, generation)
|
||||
|
||||
def __init__(self, page, channel, js_runner=None):
|
||||
"""
|
||||
:param page: QWebEnginePage 实例(QtWebEngine 路径)
|
||||
@@ -18,6 +21,7 @@ class ChatBridge(QObject):
|
||||
super().__init__()
|
||||
self.page = page
|
||||
self._js_runner = js_runner
|
||||
self.window = None # 🆕 P1-01:MainWindow 引用(分页请求需要访问 DB/代次)
|
||||
if channel is not None:
|
||||
# 将自身注册到 channel 中,前端可通过 bridge 对象调用
|
||||
channel.registerObject("bridge", self)
|
||||
@@ -157,8 +161,46 @@ class ChatBridge(QObject):
|
||||
"""隐藏加载界面(带渐变退场)"""
|
||||
self.run_js("hideLoadingOverlay();")
|
||||
|
||||
# ---------- 🆕 P1-01 渲染窗口 (Python -> JS) ----------
|
||||
|
||||
def rw_config(self, mode: str, size: int):
|
||||
"""注入渲染窗口配置(JS 就绪后、首次加载前调用一次)"""
|
||||
self.run_js("rwApplyConfig(" + json.dumps(
|
||||
{"render_window_mode": mode, "render_window_size": size}) + ");")
|
||||
|
||||
def rw_begin(self, session_id: str, generation: int, total: int):
|
||||
"""开始一轮窗口化加载:清屏 + 重同步 (session, generation)"""
|
||||
self.run_js(f"rwBegin({json.dumps(session_id)}, {int(generation)}, {int(total)});")
|
||||
|
||||
def rw_init_window(self, session_id: str, generation: int, chain_len: int, items):
|
||||
"""初始窗口就绪:items = [(msg_id, chain_index), ...](旧→新)"""
|
||||
payload = json.dumps({
|
||||
"sessionId": session_id,
|
||||
"generation": int(generation),
|
||||
"chainLen": int(chain_len),
|
||||
"items": [{"id": mid, "chainIndex": ix} for mid, ix in items],
|
||||
}, ensure_ascii=False)
|
||||
self.run_js(f"rwInitWindow({payload});")
|
||||
|
||||
def rw_note_live(self, session_id: str, generation: int, msg_id: str,
|
||||
chain_index: int, chain_len: int = -1):
|
||||
"""新消息追加进窗口(发送用户消息 / 助手占位 / 切回续流)。
|
||||
未持久化/未知 → chain_index=-1;未知链长 → chain_len=-1(JS 保持旧值)"""
|
||||
self.run_js(f"rwNoteLive({json.dumps(session_id)}, {int(generation)}, "
|
||||
f"{json.dumps(msg_id)}, {int(chain_index)}, {int(chain_len)});")
|
||||
|
||||
def rw_page_response(self, payload: dict):
|
||||
"""分页响应推送:payload 由 MainWindow._on_window_page_request 构造"""
|
||||
self.run_js("rwPageResponse(" + json.dumps(payload, ensure_ascii=False) + ");")
|
||||
|
||||
# ---------- 交互 Slot (JS -> Python) ----------
|
||||
|
||||
@pyqtSlot(str, str, str, int)
|
||||
def onRequestWindowPage(self, session_id, direction, boundary_msg_id, generation):
|
||||
"""🆕 P1-01:前端请求换页(direction: older/newer)。
|
||||
fire-and-forget:结果由 Python 经 rwPageResponse 推送(双引擎同构)。"""
|
||||
self.window_page_requested.emit(session_id, direction, boundary_msg_id, int(generation))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def onRegenerateClicked(self, msg_id):
|
||||
"""前端点击“重新生成”按钮时触发"""
|
||||
|
||||
Reference in New Issue
Block a user