python+PyQt实现基于Chromium内核的ai浏览器bfwbrowser代码

代码语言:python

所属分类:其他

代码描述:python+PyQt实现基于Chromium内核的ai浏览器bfwbrowser代码,支持设置自定义ai大模型api及可以,支持多个tab选项卡,支持收藏、历史记录,支持选择多个tab网页作为上下文,支持参考本地文件,支持问答与代理模式,代理模式下可自动点击输入操作网页完成任务,支持网页右键菜单等等。

代码标签: python PyQt 基于 Chromium 内核 ai 浏览器 bfwbrowser 代码

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

import sys
import json
import urllib.request
import os
import re
import time
from datetime import datetime
from PyQt5.QtCore import QUrl, pyqtSignal, Qt, QThread, QPoint, QTimer
from PyQt5.QtGui import QTextCursor, QFont
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                             QHBoxLayout, QLineEdit, QPushButton, QTextBrowser,
                             QSplitter, QLabel, QMessageBox, QDialog, QFormLayout,
                             QMenu, QTabBar, QStackedWidget, QComboBox, QTextEdit, 
                             QFileDialog, QListWidget, QListWidgetItem, QTableWidget, 
                             QTableWidgetItem, QHeaderView, QAbstractItemView, QCheckBox, QStyle)
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage

# ==========================================
# 全局数据与持久化
# ==========================================
DATA_FILE = "bfwbrowser_data.json"
DEFAULT_DATA = {
    "api_configs":[{"name": "默认 OpenAI", "base_url": "https://api.openai.com/v1", "api_key": "", "model": "gpt-3.5-turbo"}],
    "active_api_index": 0,
    "sessions":[{"title": "新会话", "messages": []}],
    "active_session_index": 0,
    "bookmarks":[],
    "history":[] 
}
APP_DATA = {}

def load_data():
    global APP_DATA
    if os.path.exists(DATA_FILE):
        try:
            with open(DATA_FILE, "r", encoding="utf-8") as f:
                APP_DATA = json.load(f)
        except: APP_DATA = DEFAULT_DATA.copy()
    else: APP_DATA = DEFAULT_DATA.copy()
    for key in DEFAULT_DATA:
        if key not in APP_DATA or not APP_DATA[key] and isinstance(DEFAULT_DATA[key], list):
            APP_DATA[key] = DEFAULT_DATA[key].copy()

def save_data():
    try:
        with open(DATA_FILE, "w", encoding="utf-8") as f: json.dump(APP_DATA, f, ensure_ascii=False, indent=4)
    except Exception as e: print("保存失败:", e)

def record_history(title, url):
    if not url or url.startswith("bfw://") or url == "about:blank": return
    now = time.strftime("%Y-%m-%d %H:%M:%S")
    if APP_DATA["history"] and APP_DATA["history"][0]["url"] == url: return
    APP_DATA["history"].insert(0, {"title": title, "url": url, "time": now})
    APP_DATA["history"] = APP_DATA["history"][:2000] 
    save_data()

load_data()

# ==========================================
# Markdown 渲染器 & 网络线程
# ==========================================
def render_markdown(text):
    text = text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
    def code_block_replacer(match):
        lang = match.group(1).strip()
        code = match.group(2).strip()
        lang_bar = f'<div style="color:#a5a5a5; font-size:12px; margin-bottom:6px; font-weight:bold;">{lang if lang else "Code"}</div>'
        return f'<table width="100%" bgcolor="#1e1e1e" cellpadding="10" cellspacing="0" style="margin:8px 0; border-radius:6px;"><tr><td>{lang_bar}<pre style="color:#d4d4d4; font-family:Consolas, monospace; font-size:13px; margin:0;">{code}</pre></td></tr></table>'
    text = re.sub(r'```([A-Za-z0-9+#\-]*)\n(.*?)```', code_block_replacer, text, flags=re.DOTALL)
    text = re.sub(r'```(.*?)```', code_block_replacer, text, flags=re.DOTALL)
    text = re.sub(r'`(.*?)`', r'<code style="background-color:#f0f2f5; color:#e83e8c; font-family:Consolas, monospace; font-size:13px;"> \1 </code>', text)
    text = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', text)
    text = re.sub(r'\*(.*?)\*', r'<i>\1</i>', text)
    text = re.sub(r'^### (.*?)$', r'<h3 style="color:#2c3e50; margin:10px 0;">\1</h3>', text, flags=re.MULTILINE)
    text = re.sub(r'^## (.*?)$', r'<h2 style="color:#2c3e50; margin:12px 0;">\1</h2>', text, flags=re.MULTILINE)
    text = re.sub(r'^# (.*?)$', r'<h1 style="color:#2c3e50; margin:14px 0;">\1</h1>', text, flags=re.MULTILINE)
    parts = re.split(r'(<table.*?</table>)', text, flags=re.DOTALL)
    for i in range(len(parts)):
        if not parts[i].startswith('<table'): parts[i] = parts[i].replace('\n', '<br>')
    return "".join(parts)

class LLMWorkerThread(QThread):
    stream_signal = pyqtSignal(str)
    finished_signal = pyqtSignal()
    error_signal = pyqtSignal(str)
    def __init__(self, messages):
        super().__init__()
        self.messages = messages
    def run(self):
            api_idx = APP_DATA.get("active_api_index", 0)
            config = APP_DATA["api_configs"][api_idx] if api_idx < len(APP_DATA["api_configs"]) else APP_DATA["api_configs"][0]
            if not config.get("api_key"):
                self.error_signal.emit(f"未配置 API Key,请点击 '⚙ 设置' 配置!")
                return
            url = f"{config['base_url'].rstrip('/')}/chat/completions"
            headers = { "Content-Type": "application/json", "Authorization": f"Bearer {config['api_key']}" }
            data = { "model": config["model"], "messages": self.messages, "stream": True }
            try:
                req = urllib.request.Request(url, data=json.dumps(data).encode('utf-8'), headers=headers)
                with urllib.request.urlopen(req, timeout=60) as response:
                    while True:
                        line = response.readline()
                        if not line: break
                        
                        line .........完整代码请登录后点击上方下载按钮下载查看

网友评论0