├── CMakeLists.txt ├── README.md ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── screenshort1.jpg └── screenshort2.jpg /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(AIChat VERSION 0.1 LANGUAGES CXX) 4 | 5 | set(CMAKE_AUTOUIC ON) 6 | set(CMAKE_AUTOMOC ON) 7 | set(CMAKE_AUTORCC ON) 8 | 9 | set(CMAKE_CXX_STANDARD 17) 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | 12 | find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) 13 | find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets WebEngineWidgets) 14 | 15 | set(PROJECT_SOURCES 16 | main.cpp 17 | mainwindow.cpp 18 | mainwindow.h 19 | ) 20 | 21 | if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) 22 | qt_add_executable(AIChat 23 | MANUAL_FINALIZATION 24 | ${PROJECT_SOURCES} 25 | ) 26 | # Define target properties for Android with Qt 6 as: 27 | # set_property(TARGET AIChat APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR 28 | # ${CMAKE_CURRENT_SOURCE_DIR}/android) 29 | # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation 30 | else() 31 | if(ANDROID) 32 | add_library(AIChat SHARED 33 | ${PROJECT_SOURCES} 34 | ) 35 | # Define properties for Android with Qt 5 after find_package() calls as: 36 | # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") 37 | else() 38 | add_executable(AIChat 39 | ${PROJECT_SOURCES} 40 | ) 41 | endif() 42 | endif() 43 | 44 | target_link_libraries(AIChat PRIVATE Qt${QT_VERSION_MAJOR}::Widgets 45 | Qt6::WebEngineWidgets) 46 | 47 | set_target_properties(AIChat PROPERTIES 48 | MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com 49 | MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} 50 | MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} 51 | MACOSX_BUNDLE TRUE 52 | WIN32_EXECUTABLE TRUE 53 | ) 54 | 55 | install(TARGETS AIChat 56 | BUNDLE DESTINATION . 57 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) 58 | 59 | if(QT_VERSION_MAJOR EQUAL 6) 60 | qt_finalize_executable(AIChat) 61 | endif() 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TotalAIChat 2 | ## 功能介绍 3 | - Qt6 +QWebViewEngine+C++ 4 | - 集成了8个国内主要的聊天机器人(问心一言、腾讯元宝、通义千问、豆包、Kimi、开工、360、讯飞) 5 | - 一个输入框,同时发送给8个AI机器人 6 | - 需要自己去登录8个AI机器人 7 | - 有网页是禁用了devtool,可以禁止 8 | 9 | ## 效果截图 10 | ![image](https://github.com/EffiDataEase/TotalAIChat/blob/main/screenshort1.jpg) 11 | ![image](https://github.com/EffiDataEase/TotalAIChat/blob/main/screenshort2.jpg) 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | MainWindow w; 9 | //w.showFullScreen(); 10 | w.showMaximized(); 11 | return a.exec(); 12 | } 13 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | MainWindow::MainWindow(QWidget *parent) 19 | : QMainWindow(parent) 20 | { 21 | setWindowTitle("AI Chat"); 22 | AddMenu(); 23 | AddToolBar(); 24 | InitProfile(); 25 | InitPageData(); 26 | AddWebView(); 27 | ResizeDockWidget(); 28 | SaveCurrentStatus(); 29 | } 30 | 31 | 32 | MainWindow::~MainWindow() 33 | { 34 | QSettings settings("EffiDataEase", "AIChat"); 35 | settings.setValue("LastState", this->saveState(1)); 36 | } 37 | 38 | void MainWindow::SaveCurrentStatus(){ 39 | QSettings settings("EffiDataEase", "AIChat"); 40 | restoreState(settings.value("LastState").toByteArray(),1); 41 | } 42 | 43 | void MainWindow::AddToolBar(){ 44 | // 创建一个工具栏 45 | mToolBar = new QToolBar("工具栏", this); 46 | mToolBar->setFloatable(true); 47 | mToolBar->setMovable(true); 48 | 49 | QIcon restoreIcon = style()->standardIcon(QStyle::SP_DialogResetButton); 50 | QIcon exitIcon = style()->standardIcon(QStyle::SP_DialogCloseButton); 51 | 52 | // 创建动作 (Action) 53 | QAction *exitAction = new QAction(exitIcon, "&Close", this); 54 | QAction *restoreAction = new QAction(restoreIcon, "&Restore", this); 55 | 56 | // 将动作连接到槽函数 57 | connect(exitAction, &QAction::triggered, this, &MainWindow::close); 58 | connect(restoreAction, &QAction::triggered, this, &MainWindow::OnResetView); 59 | 60 | // 将动作添加到工具栏 61 | mToolBar->addAction(restoreAction); 62 | mToolBar->addAction(exitAction); 63 | 64 | // 将工具栏添加到主窗口 65 | addToolBar(mToolBar); 66 | 67 | mToolBar->setObjectName("toolbar"); 68 | mToolBar->hide(); 69 | } 70 | 71 | void MainWindow::AddMenu(){ 72 | // 创建菜单栏 73 | QMenuBar *menuBar = new QMenuBar(this); 74 | 75 | // 创建 "File" 菜单 76 | QMenu *fileMenu = new QMenu("&文件", this); 77 | QAction *exitAction = new QAction("&退出", this); 78 | exitAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q)); 79 | connect(exitAction, &QAction::triggered, this, &MainWindow::OnExit); 80 | fileMenu->addAction(exitAction); 81 | menuBar->addMenu(fileMenu); 82 | 83 | QMenu *viewMenu = new QMenu("&视图", this); 84 | 85 | mToolbarAction = new QAction("&显示工具栏", this); 86 | connect(mToolbarAction, &QAction::triggered, this, &MainWindow::OnToolBarShow); 87 | viewMenu->addAction(mToolbarAction); 88 | 89 | auto reset_action = new QAction("&重置视图", this); 90 | connect(reset_action, &QAction::triggered, this, &MainWindow::OnResetView); 91 | viewMenu->addAction(reset_action); 92 | 93 | 94 | mFloatAction = new QAction("&悬浮", this); 95 | connect(mFloatAction, &QAction::triggered, this, &MainWindow::OnChangeDockStyle); 96 | viewMenu->addAction(mFloatAction); 97 | 98 | menuBar->addMenu(viewMenu); 99 | 100 | // 设置窗口的菜单栏 101 | setMenuBar(menuBar); 102 | } 103 | 104 | 105 | void MainWindow::InitPageData(){ 106 | 107 | auto page = new AIPage(mProfile, new MyWebView(this)); 108 | page->mName = "智谱轻言"; 109 | page->mUrl = "https://chatglm.cn/main/alltoolsdetail?lang=zh"; 110 | page->mCheckReadyjs = R"({ var visibleElement = document.querySelector('textarea[data-v-8de3e874]'); 111 | if (visibleElement) { 112 | console.log('***page ready***'); 113 | }})"; 114 | page->mPrejs = ""; 115 | 116 | page->mEditjs = R"({ var textarea = document.querySelector('textarea[data-v-8de3e874]'); 117 | textarea.value="%1"; 118 | textarea.dispatchEvent(new Event('input', { bubbles: true }));})"; 119 | 120 | page->mButtonjs = R"({ var visibleElement = document.querySelector('img[data-v-8de3e874]'); 121 | visibleElement.click();})"; 122 | mPageList.push_back(page); 123 | 124 | page = new AIPage(mProfile,new MyWebView(this)); 125 | page->mName = "天工AI"; 126 | page->mUrl = "https://www.tiangong.cn/chat/universal/016"; 127 | page->mCheckReadyjs = R"({ var visibleElement = document.querySelector('.el-textarea__inner'); 128 | if (visibleElement) { 129 | console.log('***page ready***'); 130 | } })"; 131 | page->mPrejs = ""; 132 | page->mEditjs = R"({ var textarea = document.querySelector('.el-textarea__inner'); 133 | textarea.value="%1"; 134 | textarea.dispatchEvent(new Event('input', { bubbles: true }));})"; 135 | page->mButtonjs = R"({ const button = document.querySelector('[class*="sendBtn"]'); 136 | button.click();})"; 137 | mPageList.push_back(page); 138 | 139 | 140 | 141 | page = new AIPage(mProfile,new MyWebView(this)); 142 | page->mName = "腾讯元宝"; 143 | page->mUrl = "https://yuanbao.tencent.com/"; 144 | page->mPrejs = ""; 145 | page->mCheckReadyjs = R"({ var visibleElement = document.querySelector('[class="ql-editor ql-blank"]'); 146 | if (visibleElement) { 147 | console.log('***page ready***'); 148 | }})"; 149 | page->mEditjs = R"({ var visibleElement = document.querySelector('[class="ql-editor ql-blank"]'); 150 | var dT = new DataTransfer(); 151 | var evt = new ClipboardEvent('paste', {clipboardData: dT}); 152 | evt.clipboardData.setData('text/plain', '%1'); 153 | visibleElement.dispatchEvent(evt);})"; 154 | page->mButtonjs = R"({ const button = document.querySelector('.style__send-btn___GVH0r'); 155 | button.click();})"; 156 | mPageList.push_back(page); 157 | 158 | page = new AIPage(mProfile,new MyWebView(this)); 159 | page->mName = "Kimi"; 160 | page->mUrl = "https://kimi.moonshot.cn/"; 161 | page->mPrejs = ""; 162 | page->mCheckReadyjs = R"({var visibleElement = document.querySelector('div.editorContentEditable___FZJd9'); 163 | if (visibleElement) { 164 | console.log('***page ready***'); 165 | }})"; 166 | page->mEditjs = R"({var text_div = document.querySelector('div.editorContentEditable___FZJd9'); 167 | text_div.focus(); 168 | 169 | var dT = null; 170 | try{ 171 | dT = new DataTransfer(); 172 | } catch(e){ 173 | 174 | } 175 | var evt = new ClipboardEvent('paste', {clipboardData: dT}); 176 | evt.clipboardData.setData('text/plain', '%1'); 177 | text_div.dispatchEvent(evt);})"; 178 | page->mButtonjs = R"({ const button = document.querySelector('#send-button'); 179 | if (button) { 180 | button.click(); 181 | } else { 182 | alert("not faund")}})"; 183 | mPageList.push_back(page); 184 | 185 | page = new AIPage(mProfile,new MyWebView(this)); 186 | page->mName = "通义千问"; 187 | page->mUrl = "https://tongyi.aliyun.com/qianwen/"; 188 | page->mPrejs = ""; 189 | page->mCheckReadyjs = R"({ var visibleElement = document.querySelector('.ant-input.css-1r287do.ant-input-outlined.textarea--g7EUvnQR.fadeIn--_9l6unkO'); 190 | if (visibleElement) { 191 | console.log('***page ready***'); 192 | }})"; 193 | page->mEditjs = R"({ var textarea = document.querySelector('.ant-input.css-1r287do.ant-input-outlined.textarea--g7EUvnQR.fadeIn--_9l6unkO'); 194 | function setNativeValue(element, value) { 195 | const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set; 196 | const prototype = Object.getPrototypeOf(element); 197 | const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set; 198 | 199 | if (valueSetter && valueSetter !== prototypeValueSetter) { 200 | prototypeValueSetter.call(element, value); 201 | } else { 202 | valueSetter.call(element, value); 203 | } 204 | } 205 | setNativeValue(textarea,"%1"); 206 | textarea.dispatchEvent(new Event('input', { bubbles: true }));})"; 207 | page->mButtonjs = R"({const button = document.querySelector('.operateBtn--zFx6rSR0'); 208 | button.click();})"; 209 | mPageList.push_back(page); 210 | 211 | page = new AIPage(mProfile,new MyWebView(this)); 212 | page->mName = "豆包"; 213 | page->mUrl = "https://www.doubao.com/chat/"; 214 | page->mPrejs = ""; 215 | page->mCheckReadyjs = R"({ var visibleElement = document.querySelector('textarea[data-testid="chat_input_input"]'); 216 | if (visibleElement) { 217 | console.log('***page ready***'); 218 | }})"; 219 | page->mEditjs = R"({ var textarea = document.querySelector('textarea[data-testid="chat_input_input"]'); 220 | function setNativeValue(element, value) { 221 | const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set; 222 | const prototype = Object.getPrototypeOf(element); 223 | const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set; 224 | 225 | if (valueSetter && valueSetter !== prototypeValueSetter) { 226 | prototypeValueSetter.call(element, value); 227 | } else { 228 | valueSetter.call(element, value); 229 | } 230 | } 231 | setNativeValue(textarea,"%1"); 232 | textarea.dispatchEvent(new Event('input', { bubbles: true })); })"; 233 | 234 | page->mButtonjs = R"({ var button = document.getElementById('flow-end-msg-send'); 235 | button.click();})"; 236 | mPageList.push_back(page); 237 | 238 | page = new AIPage(mProfile,new MyWebView(this)); 239 | page->mName = "360"; 240 | page->mUrl = "https://chat.360.com"; 241 | page->mPrejs = ""; 242 | page->mCheckReadyjs = R"({ var visibleElement = document.querySelector('div[contenteditable="true"]'); 243 | if (visibleElement) { 244 | console.log('***page ready***'); 245 | } })"; 246 | page->mEditjs = R"({ var visibleElement = document.querySelector('div[contenteditable="true"]'); 247 | var dT = new DataTransfer(); 248 | var evt = new ClipboardEvent('paste', {clipboardData: dT}); 249 | evt.clipboardData.setData('text/plain', '%1'); 250 | visibleElement.dispatchEvent(evt);})"; 251 | 252 | page->mButtonjs = R"({ const buttons = document.getElementsByClassName('p-8px'); 253 | const buttonElement = Array.from(buttons).find(btn => btn.classList.contains('rounded-6px') && btn.classList.contains('bg-[#0E6CF2]')); 254 | buttonElement.click();})"; 255 | mPageList.push_back(page); 256 | 257 | page = new AIPage(mProfile,new MyWebView(this)); 258 | page->mName = "讯飞"; 259 | page->mUrl = "https://xinghuo.xfyun.cn/desk"; 260 | page->mCheckReadyjs = R"({ var visibleElement = document.querySelector('#ask-window > div.ask-window_small_size_flag__4zwJQ'); 261 | if (visibleElement) { 262 | console.log('***page ready***'); 263 | }})"; 264 | page->mPrejs = R"({ var visibleElement = document.querySelector('#ask-window > div.ask-window_small_size_flag__4zwJQ'); 265 | visibleElement.click();})"; 266 | page->mEditjs = R"({ var visibleElement = document.querySelector('.ask-window_ask_window__tgBcr textarea'); 267 | visibleElement.value = "%1";})"; 268 | page->mButtonjs = R"({ var visibleElement = document.querySelector('#ask_window_send_btn'); 269 | visibleElement.click();})"; 270 | mPageList.push_back(page); 271 | 272 | page = new AIPage(mProfile,new MyWebView(this)); 273 | page->mName = "文心一言"; 274 | page->mUrl = "https://yiyan.baidu.com/"; 275 | page->mPrejs = ""; 276 | page->mCheckReadyjs = R"({ 277 | var visibleElement = document.querySelector('div.yc-editor'); 278 | if (visibleElement) { 279 | console.log('***page ready***'); 280 | } 281 | })"; 282 | page->mEditjs = R"({ 283 | var text_div = document.querySelector('div.yc-editor'); 284 | text_div.focus(); 285 | var dT = null; 286 | try{ 287 | dT = new DataTransfer(); 288 | } catch(e){ 289 | 290 | } 291 | var evt = new ClipboardEvent('paste', {clipboardData: dT}); 292 | evt.clipboardData.setData('text/plain', '%1'); 293 | text_div.dispatchEvent(evt); 294 | })"; 295 | page->mButtonjs = R"( 296 | { 297 | var spanElement = document.querySelector('span.VAtmtpqL'); 298 | if (spanElement){ 299 | spanElement.click(); 300 | } 301 | })"; 302 | mPageList.push_back(page); 303 | } 304 | 305 | void MainWindow::ResizeDockWidget() { 306 | for (int i = 0; i < mPageList.size(); i++) { 307 | auto dock = mPageList[i]->mDockWidget; 308 | if (mPageStyle == kInputEmbed_PageEmed) { 309 | if (i % 3 == 0) { 310 | addDockWidget(Qt::LeftDockWidgetArea, dock); 311 | } 312 | else { 313 | splitDockWidget(mPageList[i - 1]->mDockWidget, dock, Qt::Horizontal); 314 | } 315 | } 316 | else { 317 | if (i == 0) { 318 | addDockWidget(Qt::LeftDockWidgetArea, dock); 319 | } 320 | else { 321 | tabifyDockWidget(mPageList[i - 1]->mDockWidget, dock); 322 | } 323 | } 324 | } 325 | 326 | if (mPageStyle == kInputEmbed_PageEmed) { 327 | mInputDockWidget->setFloating(false); 328 | splitDockWidget(mPageList[6]->mDockWidget, mInputDockWidget, Qt::Horizontal); 329 | 330 | QList docks; 331 | docks << mPageList[0]->mDockWidget << mPageList[3]->mDockWidget << mPageList[6]->mDockWidget; 332 | QList sizes; 333 | sizes << height() / 3 << height() / 3 << height() / 3; 334 | resizeDocks(docks, sizes, Qt::Vertical); 335 | 336 | docks.clear(); 337 | sizes.clear(); 338 | docks << mPageList[6]->mDockWidget << mInputDockWidget << mPageList[7]->mDockWidget; 339 | sizes << width() * 2 / 5 << width() / 5 << width() * 2 / 5; 340 | resizeDocks(docks, sizes, Qt::Horizontal); 341 | 342 | } 343 | else { 344 | mInputDockWidget->setFloating(true); 345 | } 346 | } 347 | 348 | void MainWindow::AddWebView(){ 349 | 350 | for(int i=0;imName, this); 352 | dock->setObjectName(mPageList[i]->mName); 353 | mPageList[i]->mDockWidget = dock; 354 | mPageList[i]->LaunchPage(); 355 | dock->setWidget(mPageList[i]->mWebView); 356 | } 357 | 358 | QString input_name = "输入"; 359 | mInputDockWidget = new QDockWidget(input_name, this); 360 | auto pEdit = new CustomTextEdit(mInputDockWidget); 361 | mInputDockWidget->setWidget(pEdit); 362 | mInputDockWidget->setObjectName(input_name); 363 | QObject::connect(pEdit,&CustomTextEdit::SendMsg,this,&MainWindow::OnSendMsg); 364 | } 365 | 366 | void MainWindow::OnExit(){ 367 | this->close(); 368 | } 369 | void MainWindow::OnToolBarShow(){ 370 | if(mToolBar->isHidden()){ 371 | mToolBar->show(); 372 | mToolbarAction->setText("&隐藏工具栏"); 373 | }else{ 374 | mToolBar->hide(); 375 | mToolbarAction->setText("&显示工具栏"); 376 | } 377 | } 378 | void MainWindow::OnResetView(){ 379 | QSettings settings("EffiDataEase", "AIChat"); 380 | restoreState(settings.value("InitState").toByteArray(),0); 381 | } 382 | 383 | void MainWindow::OnChangeDockStyle() { 384 | if (mPageStyle == kInputEmbed_PageEmed) { 385 | mPageStyle = kInputFloat_PageTable; 386 | mFloatAction->setText("&嵌入"); 387 | } 388 | else { 389 | mPageStyle = kInputEmbed_PageEmed; 390 | mFloatAction->setText("&浮动"); 391 | } 392 | ResizeDockWidget(); 393 | } 394 | 395 | void MainWindow::InitProfile(){ 396 | //qputenv("QTWEBENGINE_REMOTE_DEBUGGING", QByteArray::number(9000)); 397 | QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); 398 | QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true); 399 | QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); 400 | 401 | QString name = QString("AIChat.") + QString(qWebEngineChromiumVersion()); 402 | mProfile = new QWebEngineProfile(name); 403 | mProfile->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); 404 | mProfile->settings()->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true); 405 | mProfile->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); 406 | mProfile->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, false); 407 | mProfile->settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); 408 | } 409 | 410 | 411 | void MainWindow::OnSendMsg(const QString &msg){ 412 | for(auto it:mPageList){ 413 | it->SendMsg(msg); 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | class AIPage; 17 | 18 | class CustomTextEdit : public QTextEdit { 19 | Q_OBJECT 20 | 21 | public: 22 | CustomTextEdit(QWidget *parent = nullptr) : QTextEdit(parent) { 23 | 24 | } 25 | 26 | signals: 27 | void SendMsg(const QString& msg); 28 | 29 | protected: 30 | void keyPressEvent(QKeyEvent *event) override { 31 | if (event->key() == Qt::Key_Return) { 32 | if (event->modifiers() & Qt::ShiftModifier) { 33 | // Shift + Enter: 换行 34 | QTextEdit::keyPressEvent(event); 35 | } else { 36 | // Enter: 弹出消息框 37 | //QMessageBox::information(this, "提示", "你按下了 Enter 键!"); 38 | emit SendMsg(this->toPlainText()); 39 | this->clear(); 40 | } 41 | } else { 42 | // 处理其他按键 43 | QTextEdit::keyPressEvent(event); 44 | } 45 | } 46 | }; 47 | 48 | 49 | class AIPage : public QWebEnginePage 50 | { 51 | Q_OBJECT 52 | public: 53 | explicit AIPage(QWebEngineProfile* profile = nullptr,QWebEngineView *parent = nullptr) 54 | :QWebEnginePage(profile,parent), 55 | mProfile(profile), 56 | mWebView(parent){ 57 | 58 | } 59 | 60 | void LaunchPage(){ 61 | //QWebEnginePage* pPage = new QWebEnginePage(mProfile,mWebView); 62 | mWebView->setPage(this); 63 | mWebView->load(QUrl(mUrl)); 64 | mWebView->setFocus(); 65 | 66 | QObject::connect(mWebView, &QWebEngineView::loadFinished, [this] { 67 | AddTimer(); 68 | }); 69 | } 70 | 71 | void DisableArkDebug(){ 72 | QString disable_antidebug = R"( //禁止反调试 73 | let end = setTimeout(function () { }, 10000); 74 | for (let i = 1; i <= end; i++) { 75 | clearTimeout(i); 76 | })"; 77 | 78 | this->runJavaScript(disable_antidebug); 79 | } 80 | 81 | void SendMsg(const QString &msg){ 82 | if(!mReady){ 83 | return; 84 | } 85 | 86 | if(!mPrejs.isEmpty()){ 87 | runJavaScript(mPrejs); 88 | } 89 | 90 | QString send_msg = msg; 91 | QTimer::singleShot(100, [this,send_msg] { 92 | this->runJavaScript(mEditjs.arg(send_msg)); 93 | }); 94 | 95 | QTimer::singleShot(500, [this] { 96 | this->runJavaScript(mButtonjs); 97 | }); 98 | } 99 | 100 | void AddTimer(){ 101 | if(mTimer == nullptr){ 102 | mTimer = new QTimer(this); 103 | mTimer->setSingleShot(false); // 设置为重复定时器 104 | mTimer->setInterval(1000); 105 | connect(mTimer, &QTimer::timeout, this, &AIPage::OnTime); 106 | } 107 | mTimer->start(); // 启动定时器 108 | } 109 | 110 | protected slots: 111 | void OnTime(){ 112 | runJavaScript(mCheckReadyjs); 113 | } 114 | 115 | protected: 116 | virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, 117 | const QString &message, 118 | int lineNumber, 119 | const QString &sourceID) override { 120 | if (message.contains("***page ready***") ) { 121 | mReady = true; 122 | if(mTimer != nullptr){ 123 | mTimer->stop(); 124 | } 125 | } 126 | return QWebEnginePage::javaScriptConsoleMessage(level,message,lineNumber,sourceID); 127 | } 128 | 129 | public: 130 | QString mName; 131 | QString mUrl; 132 | QString mCheckReadyjs; 133 | QString mPrejs; 134 | QString mEditjs; 135 | QString mButtonjs; 136 | bool mReady = false; 137 | QWebEngineProfile* mProfile = nullptr; 138 | QWebEngineView* mWebView = nullptr; 139 | QTimer* mTimer = nullptr; 140 | QDockWidget* mDockWidget = nullptr; 141 | }; 142 | 143 | class MyWebView : public QWebEngineView 144 | { 145 | Q_OBJECT 146 | public: 147 | MyWebView(QWidget* parent = nullptr, QWebEngineProfile* profile = nullptr) : 148 | QWebEngineView(parent) 149 | , m_profile(profile) { 150 | 151 | connect(this->page(), &QWebEnginePage::linkHovered, this, &MyWebView::linkHovered); 152 | } 153 | 154 | protected: 155 | QWebEngineView* createWindow(QWebEnginePage::WebWindowType type) override { 156 | QWebEnginePage* pPage2 = new AIPage(m_profile, this); 157 | this->setPage(pPage2); 158 | this->load(now_url); 159 | this->setFocus(); 160 | 161 | return this; 162 | } 163 | 164 | void contextMenuEvent(QContextMenuEvent* event) override { 165 | QMenu contextMenu(this); 166 | 167 | // 添加自定义菜单项 168 | QAction* copyAction = contextMenu.addAction(tr("&Reload")); 169 | connect(copyAction, &QAction::triggered, this, &MyWebView::ReloadUrl); 170 | 171 | // 显示自定义的上下文菜单 172 | contextMenu.exec(event->globalPos()); 173 | 174 | // 阻止默认的上下文菜单显示 175 | event->accept(); 176 | } 177 | 178 | public slots: 179 | void linkHovered(const QString& url) 180 | { 181 | now_url = url; 182 | } 183 | 184 | void ReloadUrl() { 185 | auto pAiPage = qobject_cast(this->page()); 186 | pAiPage->load(pAiPage->mUrl); 187 | } 188 | 189 | protected: 190 | QUrl now_url; 191 | QWebEngineProfile* m_profile = nullptr; 192 | }; 193 | 194 | 195 | class MainWindow : public QMainWindow 196 | { 197 | Q_OBJECT 198 | 199 | public: 200 | MainWindow(QWidget *parent = nullptr); 201 | ~MainWindow(); 202 | 203 | enum{ 204 | kInputFloat_PageTable, 205 | kInputEmbed_PageEmed, 206 | }; 207 | 208 | protected: 209 | void InitPageData(); 210 | void AddMenu(); 211 | void AddToolBar(); 212 | void InitProfile(); 213 | void AddWebView(); 214 | void SaveCurrentStatus(); 215 | void ResizeDockWidget(); 216 | 217 | private slots: 218 | void OnExit(); 219 | void OnSendMsg(const QString &msg); 220 | void OnToolBarShow(); 221 | void OnResetView(); 222 | void OnChangeDockStyle(); 223 | 224 | protected: 225 | QWebEngineProfile* mProfile = nullptr; 226 | std::vector mPageList; 227 | QToolBar *mToolBar = nullptr; 228 | QAction *mToolbarAction = nullptr; 229 | QAction* mFloatAction = nullptr; 230 | QDockWidget* mInputDockWidget = nullptr; 231 | int mPageStyle = kInputEmbed_PageEmed; 232 | }; 233 | #endif // MAINWINDOW_H 234 | -------------------------------------------------------------------------------- /screenshort1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EffiDataEase/TotalAIChat/6f60d672ec8f8d6f31748ba8d2067173909d70c1/screenshort1.jpg -------------------------------------------------------------------------------- /screenshort2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EffiDataEase/TotalAIChat/6f60d672ec8f8d6f31748ba8d2067173909d70c1/screenshort2.jpg --------------------------------------------------------------------------------