#include "kateai.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backend.h" #include "exllama.h" #include "kateaiconfigpage.h" K_PLUGIN_FACTORY_WITH_JSON(KateAiPluginFactory, "kateai.json", registerPlugin();) KateAiPlugin::KateAiPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent), m_serverUrl(QStringLiteral("ws://localhost:8642")) { m_ai = new ExLlama(); KateAiPluginView::setAi(m_ai); readConfig(); } KateAiPlugin::~KateAiPlugin() = default; void KateAiPlugin::reconnect() { m_ai->open(m_serverUrl); } QObject *KateAiPlugin::createView(KTextEditor::MainWindow *mainWindow) { auto view = new KateAiPluginView(this, mainWindow); connect(view, &KateAiPluginView::reconnect, this, &KateAiPlugin::reconnect); return view; } KTextEditor::ConfigPage *KateAiPlugin::configPage(int number, QWidget *parent) { if (number != 0) return nullptr; return new KateAiConfigPage(parent, this); } void KateAiPlugin::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), "Ai"); m_serverUrl = QUrl(config.readEntry("Url", "ws://localhost:8642")); reconnect(); } KateAiPluginView::KateAiPluginView(KateAiPlugin *plugin, KTextEditor::MainWindow *mainwindow, bool instruct) : QObject(plugin) , m_mainWindow(mainwindow) , m_useInstruct(instruct) { KXMLGUIClient::setComponentName(QStringLiteral("kateaiplugin"), QStringLiteral("Kate Ai")); setXMLFile(QStringLiteral("ui.rc")); QAction *generateAction = actionCollection()->addAction(QStringLiteral("ai_generate")); generateAction->setText(QStringLiteral("Generate text using AI")); actionCollection()->setDefaultShortcut(generateAction, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_A)); m_mainWindow->guiFactory()->addClient(this); connect(generateAction, &QAction::triggered, this, &KateAiPluginView::generate); connect(m_ai, &AiBackend::gotResponse, this, &KateAiPluginView::gotResponse); } QPointer KateAiPluginView::activeDocument() const { KTextEditor::View *view = m_mainWindow->activeView(); if(view && view->document()) return view->document(); return nullptr; } KTextEditor::Cursor KateAiPluginView::getCurrentCursor() const { KTextEditor::View *view = m_mainWindow->activeView(); if(view) return view->cursorPosition(); return KTextEditor::Cursor(); } void KateAiPluginView::gotResponse(AiBackend::Response response) { QHash::iterator it = m_requests.find(response.getId()); if(it != m_requests.end()) { it.value().document->insertText(it.value().cursor, response.getText()); if(response.isFinished()) m_requests.erase(it); else it.value().cursor = getCurrentCursor(); } } QStringList KateAiPluginView::getIncludePaths(const QString& text) { QStringList lines = text.split(U'\n'); QStringList paths; for(const QString& line : lines) { if(line.trimmed().startsWith(QStringLiteral("#include"))) { int start = line.indexOf(U'<'); int end; if(start != -1) { end = line.indexOf(U'>', start+1); if(end == -1) continue; } else { start = line.indexOf(U'"'); if(start == -1) continue; end = line.indexOf(U'"', start+1); if(end == -1) continue; } paths.push_back(line.mid(start+1, (end-(start+1))).trimmed()); } } return paths; } QString KateAiPluginView::assembleContext(QPointer document, const KTextEditor::Cursor& cursor) { QString mime = document->mimeType(); QString context; QString baseText; if(!m_useInstruct) baseText = document->text(KTextEditor::Range(KTextEditor::Cursor(0, 0), cursor)); else baseText = document->text(); if(mime == QStringLiteral("text/x-c++src") || mime == QStringLiteral("text/x-csrc")) { QFileInfo documentFileInfo(document->url().path()); QString directory = documentFileInfo.absolutePath(); QStringList paths = getIncludePaths(baseText); qDebug()<<__func__<<"Directory:"<mimeType(); if(m_ai->ready()) { KTextEditor::Cursor cursor = getCurrentCursor(); QPointer document = activeDocument(); QString text = assembleContext(document, cursor); AiBackend::Request request(text); m_ai->generate(request); m_requests.insert(request.getId(), {cursor, document}); } else { QMessageBox box; box.setText(i18n("The AI server is not connected.")); box.setInformativeText(i18n("would you like to try and reconnect?")); box.setStandardButtons(QMessageBox::Yes | QMessageBox::No); box.setDefaultButton(QMessageBox::Yes); int ret = box.exec(); if(ret == QMessageBox::Yes) reconnect(); } } KateAiPluginView::~KateAiPluginView() { m_mainWindow->guiFactory()->removeClient(this); } void KateAiPluginView::setInstruct(bool instruct) { m_useInstruct = instruct; } #include "kateai.moc" #include "moc_kateai.cpp"