兴趣是最好的老师。周末晚上熬夜到半夜十二点多,花了几个钟头给NDD制作了一款AI搜索问答插件,时间仓促界面较丑,后续插件代码开源并维护和美化。Notepad记事本工具挺常用的,把AI接口接入进来使用起来方便多啦,直接在上面搜索问答。这里记录下详细过程,分享给有需要的小伙伴。
想要体验的小伙伴可以点赞加评论,首页关注公众号,回复666获取体验码。
无需注册和配置,只需我把动态库发您,放入插件目录即可,使用我的后台服务可以直接使用。
ndd-chat-ai: NDD(NotePad--)的聊天机器人插件
实现效果截图:

首先得有一个AI的后台接口服务。好在我已具备,部署在了免费的replit。如果你有公有云服务器资源,可以也部署一个AI的后台接口服务。如果没有,免费的repit也很好用。
下载NDD源码,准备好编译环境(msvc2019工具链+QT5.12以上版本)。
以下是我的replit后台截图: 
按照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_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)
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颜色对照表
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、
您认为可以作为插件很好地存在于您的Rails应用程序中必须实现的哪些行为?您过去曾搜索过哪些插件功能但找不到?哪些现有的Rails插件可以改进或扩展,如何改进或扩展? 最佳答案 我希望在管理界面中看到一个引擎插件,它提供了应用程序中所有模型的仪表板摘要,以及可配置的事件图表。 关于ruby-on-rails-您希望看到哪些Rails插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questio
require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame
我们正在使用Vagrant进行部署,我们最终希望将此集群部署在Rackspace上。vagrant-rackspace插件是一个自然的选择,但它有一些错误,这些错误未包含在最新的0.1.1版本中(notablythatvagrantprovisiondoesn'twork)。我已经在我的personalfork中解决了这个问题通过合并其他人的工作来对存储库进行改造。是否可以从github安装vagrant插件?显而易见的事情没有奏效:[unix]$vagrantplugininstallvagrant-rackspace--plugin-sourcehttps://github.com
我已经开始学习Ruby,我已经阅读了一些教程,甚至还买了一本书(“ProgrammingRuby1.9-ThePragmaticProgrammers'Guide”),我遇到了一些以前从未见过的新东西使用我知道的任何其他语言(我是一名PHP网络开发人员)。block和过程。我想我明白它们是什么,但我不明白的是为什么它们如此伟大,以及我应该在何时何地使用它们。我到处都看到他们说block和过程是Ruby中的一个很棒的特性,但我不理解它们。这里有人能给像我这样的Ruby新手一些解释吗? 最佳答案 block有很多好处。电梯演讲:bloc
文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk=Var(yt)Cov(yt,yt−k)其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞