想要体验的小伙伴可以点赞加评论,首页关注公众号,回复666获取体验码。
无需注册和配置,只需我把动态库发您,放入插件目录即可,使用我的后台服务可以直接使用。
ndd-chat-ai: NDD(NotePad--)的聊天机器人插件
实现效果截图:
环境准备
首先得有一个AI的后台接口服务。好在我已具备,部署在了免费的replit。如果你有公有云服务器资源,可以也部署一个AI的后台接口服务。如果没有,免费的repit也很好用。
下载NDD源码,准备好编译环境(msvc2019工具链+QT5.12以上版本)。
以下是我的replit后台截图:
NDD插件制作
插件主要功能实现类
按照NDD插件制作说明制作一个插件,点击菜单后弹出一个QDockWidget停靠窗口(实用工具窗口)。停靠在主窗口的左侧。NDDMyPlugin主功能实现类如下:
// // Created by Administrator on 2023/3/19. // #ifndef HELLOWORLD_NDDMYPLUGIN_H #define HELLOWORLD_NDDMYPLUGIN_H #include <QAction> #include <QObject> #include <QWidget> #include <qsciscintilla.h> class QDockWidget; class NDDMyPlugin : public QObject{ Q_OBJECT public: explicit NDDMyPlugin(QWidget *mainWidget, const QString &pluginPath, QsciScintilla *pEdit, QObject *parent = nullptr); ~NDDMyPlugin() override = default; void getViewMenu(QMenu *menu); void setScintilla(const std::function<QsciScintilla *()> &cb); private: QWidget *mainWidget_; QDockWidget *dockWidget_; private: std::function<QsciScintilla *()> scintillaCallback_; }; #endif //HELLOWORLD_NDDMYPLUGIN_H
// // Created by yangyongzhen 2023/3/19. // #include "NDDMyPlugin.h" #include "mysetting.h" #include <docktitlewidget.h> #include <QDockWidget> #include <QHeaderView> #include <QMainWindow> #include <QMenuBar> NDDMyPlugin::NDDMyPlugin(QWidget *mainWidget, const QString &pluginPath, QsciScintilla *pEdit, QObject *parent) : QObject(parent), dockWidget_(new QDockWidget("AI窗口")), mainWidget_(mainWidget) { dockWidget_->setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable); dockWidget_->setAllowedAreas(Qt::LeftDockWidgetArea); dockWidget_->hide(); auto dockWidgetTitle = new DockTitleWidget; //dockWidget_->setTitleBarWidget(dockWidgetTitle); dockWidget_->setWidget(dockWidgetTitle); auto mainWindow = dynamic_cast<QMainWindow *>(mainWidget); mainWindow->addDockWidget(Qt::LeftDockWidgetArea, dockWidget_); } void NDDMyPlugin::getViewMenu(QMenu *menu) { menu->addAction("Show Chat Window", this, [this] { dockWidget_->show(); }); menu->addAction( "快捷按键(Ctrl+F8)", this, [this] { }, Qt::CTRL + Qt::Key_F8); menu->addAction("Settings", this, [this] { //参数设置 MySettingDlg* p = new MySettingDlg(mainWidget_,scintillaCallback_()); //主窗口关闭时,子窗口也关闭。避免空指针操作 p->setWindowFlag(Qt::Window); p->show(); }); } void NDDMyPlugin::setScintilla(const std::function<QsciScintilla *()> &cb) { if(scintillaCallback_== nullptr){ scintillaCallback_ = cb; } }
插件接口类
该类主要是插件框架层接口的实现。这个简单,基本就是按NDD插件的说明文档制作。
#include <qobject.h> #include <qstring.h> #include <pluginGl.h> #include <functional> #include <qsciscintilla.h> #include "qttestclass.h" #include "NDDMyPlugin.h" #define NDD_EXPORTDLL #if defined(Q_OS_WIN) #if defined(NDD_EXPORTDLL) #define NDD_EXPORT __declspec(dllexport) #else #define NDD_EXPORT __declspec(dllimport) #endif #else #define NDD_EXPORT __attribute__((visibility("default"))) #endif #ifdef __cplusplus extern "C" { #endif NDD_EXPORT bool NDD_PROC_IDENTIFY(NDD_PROC_DATA* pProcData); NDD_EXPORT int NDD_PROC_MAIN(QWidget* pNotepad, const QString& strFileName, std::function<QsciScintilla* ()>getCurEdit, NDD_PROC_DATA* procData); #ifdef __cplusplus } #endif NDDMyPlugin *nddMyPlugin = nullptr; static NDD_PROC_DATA s_procData; static QWidget* s_pMainNotepad = nullptr; std::function<QsciScintilla* ()> s_getCurEdit; bool NDD_PROC_IDENTIFY(NDD_PROC_DATA* pProcData) { if(pProcData == NULL) { return false; } pProcData->m_strPlugName = QObject::tr("Chat AI Plug"); pProcData->m_strComment = QObject::tr("chat tool use openai GPT-3.5"); pProcData->m_version = QString("v1.0"); pProcData->m_auther = QString("yangyongzhen"); pProcData->m_menuType = 1; return true; } //则点击菜单栏按钮时,会自动调用到该插件的入口点函数。 //pNotepad:就是CCNotepad的主界面指针 //strFileName:当前插件DLL的全路径,如果不关心,则可以不使用 //getCurEdit:从NDD主程序传递过来的仿函数,通过该函数获取当前编辑框操作对象QsciScintilla //pProcData:如果pProcData->m_menuType = 0 ,则该指针为空;如果pProcData->m_menuType = 1,则该指针有值。目前需要关心s_procData.m_rootMenu //开发者可以在该菜单下面,自行创建二级菜单 int NDD_PROC_MAIN(QWidget* pNotepad, const QString &strFileName, std::function<QsciScintilla*()>getCurEdit, NDD_PROC_DATA* pProcData) { //务必拷贝一份pProcData,在外面会释放。 if (pProcData == nullptr) { return 1; } s_pMainNotepad = pNotepad; s_procData = *pProcData; s_getCurEdit = getCurEdit; //如果pProcData->m_menuType = 1;是自己要创建二级菜单的场景。则通过s_procData.m_rootMenu 获取该插件的菜单根节点。 //插件开发者自行在s_procData.m_rootMenu下添加新的二级菜单项目 //QMenu* menu = s_procData.m_rootMenu; if (!nddMyPlugin) { nddMyPlugin = new NDDMyPlugin(s_pMainNotepad, strFileName, nullptr, s_pMainNotepad); nddMyPlugin->getViewMenu(s_procData.m_rootMenu); nddMyPlugin->setScintilla(s_getCurEdit); } return 0; }
cmake编译脚本
cmake_minimum_required(VERSION 3.16) project(mychatai) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_PREFIX_PATH "D:/Qt5.12.11/Qt5.12.11/5.12.11/msvc2015_64/lib/cmake") find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Concurrent Network PrintSupport XmlPatterns) add_definitions(-D_UNICODE -DUNICODE) # win下需要开启UNICODE进行支持TCHAR if(CMAKE_HOST_WIN32) add_definitions(-D_UNICODE -DUNICODE) endif() file(GLOB UI_SRC ${PROJECT_SOURCE_DIR}/*.ui) file(GLOB SRC ${PROJECT_SOURCE_DIR}/*.cpp) file(GLOB MOC_HEADER ${PROJECT_SOURCE_DIR}/*.h) # add_executable(${PROJECT_NAME} ${IS_WIN} ${SRC} ${UI_SRC} ${PROJECT_SOURCE_DIR}/src/RealCompare.qrc) link_directories( ${CMAKE_CURRENT_SOURCE_DIR}/ ) #find_library(QSCINT_LIB qmyedit_qt5d PATH ${CMAKE_CURRENT_SOURCE_DIR}/) #add_library( qmyedit_qt5d SHARED IMPORTED ) #set_target_properties( qmyedit_qt5d PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/qmyedit_qt5d.dll ) add_library(${PROJECT_NAME} SHARED ${SRC} ${UI_SRC} ${MOC_HEADER}) target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/../../include ${PROJECT_SOURCE_DIR}/../../qscint/src ${PROJECT_SOURCE_DIR}/../../qscint/src/Qsci ${PROJECT_SOURCE_DIR}/../../qscint/scintilla/src ${PROJECT_SOURCE_DIR}/../../qscint/scintilla/include ${PROJECT_SOURCE_DIR}/../../qscint/scintilla/lexlib ${PROJECT_SOURCE_DIR}/../../qscint/scintilla/boostregex ) #set(QSCINT_LIB ${CMAKE_CURRENT_SOURCE_DIR}/qmyedit_qt5d.lib) target_link_libraries(${PROJECT_NAME} PRIVATE debug qmyedit_qt5d optimized qmyedit_qt5) link_directories( ${CMAKE_CURRENT_SOURCE_DIR}/ ) target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Concurrent Qt5::Network Qt5::PrintSupport Qt5::XmlPatterns)
QT的https访问
void DockTitleWidget::slotBtn_SendClick() { //设置头信息 QNetworkRequest m_url; //m_url.setUrl(QUrl("https://pmp.eloam.net/api/ota/findFadVersion")); m_url.setUrl(QUrl("https://xxxxx/xxxx")); m_url.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QSslConfiguration m_sslConfig = QSslConfiguration::defaultConfiguration(); m_sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); m_sslConfig.setProtocol(QSsl::TlsV1_2); m_url.setSslConfiguration(m_sslConfig); //char cByte[1024] = "{\"as\":\"123456\", \"ks\": \"123456\", \"productCode\": \"HSPS\", \"version\": \"V1.2.3\"}"; auto send = QString(u8"{\"user\":\"%1\",\"msg\":\"%2\"}").arg("yang").arg(ui->te_send->toPlainText()); QByteArray bate = send.toUtf8(); //QByteArray bate(send); //发送数据 QString fromName = QString(u8"我问:"); fromName = QString("<font color = blue>%1</font>").arg(fromName);//必须用br作为换行符 ui->te_recv->append(fromName); ui->te_recv->append(ui->te_send->toPlainText()); m_resp = m_http->post(m_url, bate); connect(m_resp, &QNetworkReply::finished, this, &DockTitleWidget::slotNet_Received); } void DockTitleWidget::slotNet_Received() { QString fromName = QString(u8"\nchat-Ai回答:"); fromName = QString("<font color = green>%1</font>").arg(fromName);//必须用br作为换行符 ui->te_recv->append(fromName); if (m_resp->error() == QNetworkReply::NoError) { QString strReceive = m_resp->readAll(); // 自行解析接口返回数据 //ui->te_recv->append(ba); //QMessageBox::warning(this, "123", ba); QJsonParseError json_error; QJsonDocument doc = QJsonDocument::fromJson(strReceive.toUtf8(), &json_error); if (!doc.isNull() && json_error.error == QJsonParseError::NoError) { if (doc.isObject()) { QJsonObject object = doc.object(); if (object.contains("text")) { // 包含指定的 key QJsonValue value = object.value("text"); if (value.isString()) { ui->te_recv->append(value.toString()); } } } } else { ui->te_recv->append(strReceive); } //auto recv = QJsonDocument::fromJson(strReceive); } else { ui->te_recv->append(m_resp->errorString()); //QMessageBox::warning(this, "123", m_resp->errorString()); } }
完整实现
// You may need to build the project (run Qt uic code generator) to get "ui_DockTitleWidget.h" resolved #pragma execution_character_set("utf-8") #include "docktitlewidget.h" #include "ui_DockTitleWidget.h" #include <QMessageBox> #include <QJsonDocument> #include <QJsonArray> #include <QJsonObject> #include <QJsonParseError> DockTitleWidget::DockTitleWidget(QWidget *parent) : QWidget(parent), ui(new Ui::DockTitleWidget) { ui->setupUi(this); ui->te_recv->setStyleSheet( "QTextEdit{padding-top:2px;background:#f7f7f7;border:none;border-radius:5px;font-size:12px;color:#292421;" "font-family:Microsoft YaHei;padding-left:5px;padding-right:5px;}");//无边框 ui->te_send->setStyleSheet( "QTextEdit{padding-top:2px;background:#f7f7f7;border:none;border-radius:5px;font-size:12px;color:#292421;" "font-family:Microsoft YaHei;padding-left:5px;padding-right:5px;}");//无边框 connect(ui->pb_send, SIGNAL(clicked(bool)), this, SLOT(slotBtn_SendClick())); connect(ui->te_recv, SIGNAL(textChanged()), this, SLOT(slotEdtReceiveTextChanged())); connect(this,SIGNAL(sigAppendText(QString)),this,SLOT(slotAppendText(QString))); m_http = new QNetworkAccessManager(); } DockTitleWidget::~DockTitleWidget() { delete ui; delete m_http; if (m_resp != nullptr) { delete m_resp; } } void DockTitleWidget::slotBtn_SendClick() { //设置头信息 QNetworkRequest m_url; //m_url.setUrl(QUrl("https://pmp.eloam.net/api/ota/findFadVersion")); m_url.setUrl(QUrl("https://weixx.xxx.repl.co/xxxx")); m_url.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QSslConfiguration m_sslConfig = QSslConfiguration::defaultConfiguration(); m_sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); m_sslConfig.setProtocol(QSsl::TlsV1_2); m_url.setSslConfiguration(m_sslConfig); //char cByte[1024] = "{\"as\":\"123456\", \"ks\": \"123456\", \"productCode\": \"HSPS\", \"version\": \"V1.2.3\"}"; auto send = QString(u8"{\"user\":\"%1\",\"msg\":\"%2\"}").arg("yang").arg(ui->te_send->toPlainText()); QByteArray bate = send.toUtf8(); //QByteArray bate(send); //发送数据 QString fromName = QString(u8"我问:"); fromName = QString("<font color = blue>%1</font>").arg(fromName);//必须用br作为换行符 ui->te_recv->append(fromName); ui->te_recv->append(ui->te_send->toPlainText()); m_resp = m_http->post(m_url, bate); connect(m_resp, &QNetworkReply::finished, this, &DockTitleWidget::slotNet_Received); } void DockTitleWidget::slotNet_Received() { QString fromName = QString(u8"\nchat-Ai回答:"); fromName = QString("<font color = green>%1</font>").arg(fromName);//必须用br作为换行符 ui->te_recv->append(fromName); if (m_resp->error() == QNetworkReply::NoError) { QString strReceive = m_resp->readAll(); // 自行解析接口返回数据 //ui->te_recv->append(ba); //QMessageBox::warning(this, "123", ba); QJsonParseError json_error; QJsonDocument doc = QJsonDocument::fromJson(strReceive.toUtf8(), &json_error); if (!doc.isNull() && json_error.error == QJsonParseError::NoError) { if (doc.isObject()) { QJsonObject object = doc.object(); if (object.contains("text")) { // 包含指定的 key QJsonValue value = object.value("text"); if (value.isString()) { ui->te_recv->append(value.toString()); } } } } else { ui->te_recv->append(strReceive); } //auto recv = QJsonDocument::fromJson(strReceive); } else { ui->te_recv->append(m_resp->errorString()); //QMessageBox::warning(this, "123", m_resp->errorString()); } } void DockTitleWidget::slotEdtReceiveTextChanged() { QTextCursor cursor = ui->te_recv->textCursor(); cursor.movePosition(QTextCursor::End); ui->te_recv->setTextCursor(cursor); } void DockTitleWidget::slotAppendText(const QString &text) { }
注意事项
由于使用了https访问网络,默认情况下会缺少两个库(libcrypto-1_1-x64.dll和libssl-1_1-x64.dll)。打包时需要把这两个动态库添加进来,否则运行不起来,运行会报错Process finished with exit code -1073741701 (0xC000007B)。该错误通常是由于所需的库文件(例如.dll文件)不存在或无法加载造成的,有可能是程序需要的特定版本的运行库缺少或者缺失了 32 位或 64 位的运行库。
其他资源
Qt--解析Json_qt json解析_Qt程序员的博客-CSDN博客
qt中的toUtf8, toLatin1, Local8bit, toUcs4_顺其自然~的博客-CSDN博客
各种常见颜色的RGB数值|RGB数值,rgb颜色表,金色rgb,rgb颜色,rgb颜色代码,各种颜色的rgb,常用颜色rgb,金属颜色rgb,rgb颜色对照表