chore: import original project baseline
Import the pre-repair source tree as the history baseline. Runtime data (data/), virtualenvs, bytecode caches and logs are gitignored so local secrets and user state stay out of the repo.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
# chat_bridge.py
|
||||
import json
|
||||
from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal
|
||||
|
||||
class ChatBridge(QObject):
|
||||
# 信号定义
|
||||
regenerate_clicked = pyqtSignal(str) # 携带需要重新回答的助手消息ID
|
||||
branch_switch_clicked = pyqtSignal(str, int) # 分支切换信号 (msg_id, direction: -1 为上一条, 1 为下一条)
|
||||
delete_message_requested = pyqtSignal(str) # 删除消息信号 (msg_id)
|
||||
attachment_clicked = pyqtSignal(str)
|
||||
scroll_changed = pyqtSignal(float, float, float) # 页面 scrollY / scrollHeight / innerHeight
|
||||
def __init__(self, page, channel, js_runner=None):
|
||||
"""
|
||||
:param page: QWebEnginePage 实例(QtWebEngine 路径)
|
||||
:param channel: QWebChannel 实例(QtWebEngine 路径)
|
||||
:param js_runner: 可选 JS 执行函数(WebView2 路径:script -> None)
|
||||
"""
|
||||
super().__init__()
|
||||
self.page = page
|
||||
self._js_runner = js_runner
|
||||
if channel is not None:
|
||||
# 将自身注册到 channel 中,前端可通过 bridge 对象调用
|
||||
channel.registerObject("bridge", self)
|
||||
print("ChatBridge被初始化")
|
||||
|
||||
def run_js(self, script: str):
|
||||
if self._js_runner is not None:
|
||||
self._js_runner(script)
|
||||
elif self.page is not None:
|
||||
self.page.runJavaScript(script)
|
||||
else:
|
||||
print("[ChatBridge] run_js: 无可用执行通道")
|
||||
|
||||
# ---------- 消息构造 (Python -> JS) ----------
|
||||
|
||||
def create_message(self, msg_id: str, role: str, text: str = "", sender_name: str = "", branch_info: dict = None):
|
||||
"""
|
||||
在前端创建一条消息容器
|
||||
:param branch_info: 格式如 {"current": 0, "total": 2},若为 None 则不显示分支切换器
|
||||
"""
|
||||
safe_text = json.dumps(text)
|
||||
safe_name = json.dumps(sender_name)
|
||||
safe_branch = json.dumps(branch_info) if branch_info else "null"
|
||||
self.run_js(f"createMessage('{msg_id}', '{role}', {safe_text}, {safe_name}, {safe_branch});")
|
||||
|
||||
def create_long_message(self, msg_id: str, role: str, text: str, sender_name: str = ""):
|
||||
safe_text = json.dumps(text)
|
||||
safe_name = json.dumps(sender_name)
|
||||
size_kb = round(len(text.encode('utf-8')) / 1024, 2)
|
||||
self.run_js(f"createLongMessage('{msg_id}', '{role}', {safe_text}, {safe_name}, {size_kb});")
|
||||
|
||||
def create_user_message_with_attachments(self, msg_id: str, text: str, attachments: list):
|
||||
safe_text = json.dumps(text)
|
||||
safe_attachments = json.dumps(attachments)
|
||||
self.run_js(f"createUserMessageWithAttachments('{msg_id}', {safe_text}, {safe_attachments});")
|
||||
|
||||
# ---------- 流式输出 (Python -> JS) ----------
|
||||
def append_token(self, msg_id: str, token: str):
|
||||
safe_token = json.dumps(token)
|
||||
self.run_js(f"appendToken('{msg_id}', {safe_token});")
|
||||
|
||||
def append_reasoning(self, msg_id: str, token: str):
|
||||
safe_token = json.dumps(token)
|
||||
self.run_js(f"appendReasoning('{msg_id}', {safe_token});")
|
||||
|
||||
def finish_message(self, msg_id: str):
|
||||
self.run_js(f"finishMessage('{msg_id}');")
|
||||
|
||||
# ---------- 工具执行事件 (Python -> JS) —— pi tool_execution_* ----------
|
||||
def tool_execution_started(self, msg_id: str, call_id: str, name: str, args: str):
|
||||
"""工具开始执行:前端在时间线当前位置插入 chip(带 call_id 对号入座)"""
|
||||
safe_call = json.dumps(call_id)
|
||||
safe_name = json.dumps(name)
|
||||
safe_args = json.dumps(args)
|
||||
self.run_js(f"toolExecutionStarted('{msg_id}', {safe_call}, {safe_name}, {safe_args});")
|
||||
|
||||
def tool_execution_updated(self, msg_id: str, call_id: str, text: str):
|
||||
"""工具执行中的增量输出(bash stdout 等)"""
|
||||
safe_call = json.dumps(call_id)
|
||||
safe_text = json.dumps(text)
|
||||
self.run_js(f"toolExecutionUpdated('{msg_id}', {safe_call}, {safe_text});")
|
||||
|
||||
def tool_execution_timed(self, msg_id: str, call_id: str,
|
||||
elapsed: int, timeout: int):
|
||||
"""🆕 bash 运行中每秒读秒:气泡摘要行刷新 N/Ts"""
|
||||
safe_call = json.dumps(call_id)
|
||||
self.run_js(f"toolExecutionTimed('{msg_id}', {safe_call}, "
|
||||
f"{int(elapsed)}, {int(timeout)});")
|
||||
|
||||
def tool_execution_finished(self, msg_id: str, call_id: str, name: str, ok: bool, text: str):
|
||||
"""工具执行结束:chip 按 call_id 标记成功/失败并显示结果摘要"""
|
||||
safe_call = json.dumps(call_id)
|
||||
safe_name = json.dumps(name)
|
||||
safe_text = json.dumps(text)
|
||||
self.run_js(f"toolExecutionFinished('{msg_id}', {safe_call}, {safe_name}, {str(ok).lower()}, {safe_text});")
|
||||
|
||||
# ---------- 时间线 (Python -> JS) ----------
|
||||
def restore_streaming_timeline(self, msg_id: str, timeline_json: str):
|
||||
"""切回进行中的会话:按时间线 JSON 恢复 思考/文本/工具 块(续流)"""
|
||||
safe_json = json.dumps(timeline_json)
|
||||
self.run_js(f"restoreStreamingTimeline('{msg_id}', {safe_json});")
|
||||
|
||||
def render_timeline_history(self, msg_id: str, timeline_json: str):
|
||||
"""历史消息:按时间线 JSON 静态渲染 思考/文本/工具 块"""
|
||||
safe_json = json.dumps(timeline_json)
|
||||
self.run_js(f"renderTimelineHistory('{msg_id}', {safe_json});")
|
||||
|
||||
# ---------- 压缩可视化 (Python -> JS) ----------
|
||||
def compaction_started(self, msg_id: str, path: str):
|
||||
"""压缩开始 → 当前消息时间线内显示「上下文压缩」思考气泡(与深度思考同款)"""
|
||||
safe_id = json.dumps(msg_id or "")
|
||||
safe_path = json.dumps(path or "")
|
||||
self.run_js(f"compactionStarted({safe_id}, {safe_path});")
|
||||
|
||||
def compaction_finished(self, msg_id: str, payload: dict):
|
||||
"""压缩完成 → 同一气泡原地更新(前→后 token + 摘要全文)"""
|
||||
safe_id = json.dumps(msg_id or "")
|
||||
safe_payload = json.dumps(payload or {}, ensure_ascii=False)
|
||||
self.run_js(f"compactionFinished({safe_id}, {safe_payload});")
|
||||
|
||||
# ---------- 系统提示 (Python -> JS) ----------
|
||||
def show_note(self, text: str):
|
||||
"""在聊天流中插入一条居中系统提示(如:已自动压缩上下文)"""
|
||||
safe_text = json.dumps(text)
|
||||
self.run_js(f"showSystemNote({safe_text});")
|
||||
|
||||
# ---------- 错误与清理 (Python -> JS) ----------
|
||||
def show_error(self, msg_id: str, error_text: str):
|
||||
safe_text = json.dumps(error_text)
|
||||
self.run_js(f"showError('{msg_id}', {safe_text});")
|
||||
|
||||
def clear_chat(self):
|
||||
self.run_js("clearChat();")
|
||||
|
||||
def delete_message(self, msg_id: str):
|
||||
"""通知前端从 DOM 中移除特定消息"""
|
||||
self.run_js(f"deleteMessage('{msg_id}');")
|
||||
|
||||
# ---------- 历史记录渲染 ----------
|
||||
|
||||
def render_history_message(self, msg_id: str, role: str, content: str, reasoning: str = "", branch_info: dict = None):
|
||||
self.create_message(msg_id, role, content, branch_info=branch_info)
|
||||
if reasoning:
|
||||
safe_reasoning = json.dumps(reasoning)
|
||||
self.run_js(f"insertThinkBlock('{msg_id}', {safe_reasoning});")
|
||||
self.finish_message(msg_id)
|
||||
|
||||
def show_welcome(self):
|
||||
self.run_js("showWelcome();")
|
||||
|
||||
# ---------- 会话加载界面 (Python -> JS) ----------
|
||||
def show_loading(self):
|
||||
"""显示统一加载界面(图标 + 从左到右扫描条)"""
|
||||
self.run_js("showLoadingOverlay();")
|
||||
|
||||
def hide_loading(self):
|
||||
"""隐藏加载界面(带渐变退场)"""
|
||||
self.run_js("hideLoadingOverlay();")
|
||||
|
||||
# ---------- 交互 Slot (JS -> Python) ----------
|
||||
|
||||
@pyqtSlot(str)
|
||||
def onRegenerateClicked(self, msg_id):
|
||||
"""前端点击“重新生成”按钮时触发"""
|
||||
print(f"Regenerate requested for: {msg_id}")
|
||||
self.regenerate_clicked.emit(msg_id)
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
def onBranchSwitch(self, msg_id, direction):
|
||||
"""前端点击分支切换箭头时触发"""
|
||||
print(f"Branch switch requested for: {msg_id}, direction: {direction}")
|
||||
self.branch_switch_clicked.emit(msg_id, direction)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def onDeleteMessageClicked(self, msg_id):
|
||||
"""接收 JS 发来的确认删除指令(🌟 已去重,原先定义了两次)"""
|
||||
print(f"Delete requested for: {msg_id}")
|
||||
self.delete_message_requested.emit(msg_id)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def onAttachmentClicked(self, meta_json_str):
|
||||
"""接收 JS 发来的附件点击事件"""
|
||||
self.attachment_clicked.emit(meta_json_str)
|
||||
|
||||
@pyqtSlot(float, float, float)
|
||||
def onScrollChanged(self, y, content_h, client_h):
|
||||
"""接收前端上报的页面滚动信息,用于自定义滚动条位置/滑块长度镜像"""
|
||||
self.scroll_changed.emit(y, content_h, client_h)
|
||||
Reference in New Issue
Block a user