diff --git a/src/base/ai/abstractllm.cpp b/src/base/ai/abstractllm.cpp index a26e1dfbc..c354635d8 100644 --- a/src/base/ai/abstractllm.cpp +++ b/src/base/ai/abstractllm.cpp @@ -9,3 +9,17 @@ AbstractLLM::AbstractLLM(QObject *parent) { qRegisterMetaType("ResponseState"); } + +void AbstractLLM::setModelState(LLMState st) +{ + if (state.loadAcquire() == st) + return; + + state.storeRelease(st); + Q_EMIT modelStateChanged(); +} + +AbstractLLM::LLMState AbstractLLM::modelState() const +{ + return static_cast(state.loadAcquire()); +} diff --git a/src/base/ai/abstractllm.h b/src/base/ai/abstractllm.h index 7a762281e..087adb368 100644 --- a/src/base/ai/abstractllm.h +++ b/src/base/ai/abstractllm.h @@ -14,6 +14,11 @@ class AbstractLLM : public QObject { Q_OBJECT public: + enum LLMState { + Idle = 0, + Busy + }; + enum ResponseState { Receiving, Success, @@ -22,6 +27,13 @@ class AbstractLLM : public QObject Canceled }; + enum Locale { + Zh, + En + }; + + using ResponseHandler = std::function; + explicit AbstractLLM(QObject *parent = nullptr); virtual ~AbstractLLM() {} @@ -30,18 +42,25 @@ class AbstractLLM : public QObject virtual bool checkValid(QString *errStr) = 0; virtual QJsonObject create(const Conversation &conversation) = 0; virtual void request(const QJsonObject &data) = 0; - virtual void request(const QString &prompt) = 0; - virtual void generate(const QString &prompt, const QString &suffix) = 0; + virtual void request(const QString &prompt, ResponseHandler handler = nullptr) = 0; + virtual void generate(const QString &prefix, const QString &suffix) = 0; // use to auto compelte virtual void setTemperature(double temperature) = 0; virtual void setStream(bool isStream) = 0; - virtual void processResponse(QNetworkReply *reply) = 0; + virtual void setLocale(Locale lc) = 0; virtual void cancel() = 0; virtual void setMaxTokens(int maxToken) = 0; virtual Conversation *getCurrentConversation() = 0; - virtual bool isIdle() = 0; + + void setModelState(LLMState st); + LLMState modelState() const; signals: - void dataReceived(const QString &data, ResponseState statu); + void customDataReceived(const QString &key, const QJsonObject &customData); + void dataReceived(const QString &data, ResponseState state); + void modelStateChanged(); + +private: + QAtomicInt state { Idle }; }; #endif diff --git a/src/base/ai/conversation.cpp b/src/base/ai/conversation.cpp index 322c1d78f..7ec3dbe57 100644 --- a/src/base/ai/conversation.cpp +++ b/src/base/ai/conversation.cpp @@ -70,6 +70,20 @@ bool Conversation::popUserData() return false; } +bool Conversation::addResponse(const QString &data) +{ + if (!data.isEmpty()) { + const QJsonDocument &document = QJsonDocument::fromJson(data.toUtf8()); + if (document.isArray()) { + conversation = document.array(); + } else { + conversation.push_back(QJsonObject({ { "role", "assistant" }, {"content", data} })); + } + return true; + } + return false; +} + QString Conversation::getLastResponse() const { if (!conversation.isEmpty() && conversation.last()["role"].toString() == "assistant") { diff --git a/src/base/ai/conversation.h b/src/base/ai/conversation.h index f368591f2..145fdfd78 100644 --- a/src/base/ai/conversation.h +++ b/src/base/ai/conversation.h @@ -28,6 +28,7 @@ class Conversation bool addUserData(const QString &data); bool popUserData(); + bool addResponse(const QString &data); QString getLastResponse() const; QByteArray getLastByteResponse() const; bool popLastResponse(); diff --git a/src/plugins/aimanager/aimanager.cpp b/src/plugins/aimanager/aimanager.cpp index 14b83fb91..6f282c965 100644 --- a/src/plugins/aimanager/aimanager.cpp +++ b/src/plugins/aimanager/aimanager.cpp @@ -5,18 +5,26 @@ #include "aimanager.h" #include "services/ai/aiservice.h" #include "openai/openaicompatiblellm.h" +#include "openai/openaicompletionprovider.h" #include "services/option/optionmanager.h" +#include "services/editor/editorservice.h" #include "option/detailwidget.h" #include "common/util/eventdefinitions.h" +#include "codegeex/codegeexllm.h" +#include "codegeex/codegeexcompletionprovider.h" #include +static const char *kCodeGeeXModelPath = "https://codegeex.cn/prod/code/chatCodeSseV3/chat"; + using namespace dpfservice; class AiManagerPrivate { public: QList models; + CodeGeeXCompletionProvider *cgcProvider = nullptr; + OpenAiCompletionProvider *oacProvider = nullptr; }; AiManager *AiManager::instance() @@ -29,6 +37,7 @@ AiManager::AiManager(QObject *parent) : QObject(parent) , d(new AiManagerPrivate) { + initCompleteProvider(); readLLMFromOption(); } @@ -52,6 +61,11 @@ AbstractLLM *AiManager::getLLM(const LLMInfo &info) if (!info.apikey.isEmpty()) llm->setApiKey(info.apikey); return llm; + } else if (info.type == LLMType::ZHIPU_CODEGEEX) { + auto llm = new CodeGeeXLLM(this); + llm->setModelName(info.modelName); + llm->setModelPath(info.modelPath); + return llm; } } @@ -100,6 +114,59 @@ void AiManager::readLLMFromOption() appendModel(info); } + for (LLMInfo defaultLLM : getDefaultLLM()) { + if (!d->models.contains(defaultLLM)) + appendModel(defaultLLM); + } + if (changed) ai.LLMChanged(); + + if (!map.value(kCATEGORY_AUTO_COMPLETE).isValid()) { + d->cgcProvider->setInlineCompletionEnabled(false); + d->oacProvider->setInlineCompletionEnabled(false); + return; + } + + auto currentCompleteLLMInfo = LLMInfo::fromVariantMap(map.value(kCATEGORY_AUTO_COMPLETE).toMap()); + if (currentCompleteLLMInfo.type == LLMType::OPENAI) { + d->oacProvider->setInlineCompletionEnabled(true); + d->oacProvider->setLLM(getLLM(currentCompleteLLMInfo)); + d->cgcProvider->setInlineCompletionEnabled(false); // codegeex completion provider use default url + } else if (currentCompleteLLMInfo.type == LLMType::ZHIPU_CODEGEEX) { + d->cgcProvider->setInlineCompletionEnabled(true); + d->oacProvider->setInlineCompletionEnabled(false); + } +} + +void AiManager::initCompleteProvider() +{ + d->cgcProvider = new CodeGeeXCompletionProvider(this); + d->oacProvider = new OpenAiCompletionProvider(this); + d->cgcProvider->setInlineCompletionEnabled(false); + d->oacProvider->setInlineCompletionEnabled(false); + + connect(&dpf::Listener::instance(), &dpf::Listener::pluginsStarted, this, [=] { + auto editorService = dpfGetService(EditorService); + d->cgcProvider->setInlineCompletionEnabled(true); + editorService->registerInlineCompletionProvider(d->cgcProvider); + editorService->registerInlineCompletionProvider(d->oacProvider); + }, Qt::DirectConnection); +} + +QList AiManager::getDefaultLLM() +{ + LLMInfo liteInfo; + liteInfo.icon = QIcon::fromTheme("codegeex_model_lite"); + liteInfo.modelName = CodeGeeXChatModelLite; + liteInfo.modelPath = kCodeGeeXModelPath; + liteInfo.type = LLMType::ZHIPU_CODEGEEX; + + LLMInfo proInfo; + proInfo.icon = QIcon::fromTheme("codegeex_model_pro"); + proInfo.modelName = CodeGeeXChatModelPro; + proInfo.modelPath = kCodeGeeXModelPath; + proInfo.type = LLMType::ZHIPU_CODEGEEX; + + return { liteInfo, proInfo }; } diff --git a/src/plugins/aimanager/aimanager.h b/src/plugins/aimanager/aimanager.h index 8a6a9744b..8506ed327 100644 --- a/src/plugins/aimanager/aimanager.h +++ b/src/plugins/aimanager/aimanager.h @@ -9,7 +9,10 @@ // option manager -> custom models static const char *kCATEGORY_CUSTOMMODELS = "CustomModels"; +static const char *kCATEGORY_AUTO_COMPLETE = "AutoComplete"; static const char *kCATEGORY_OPTIONKEY = "OptionKey"; +static const char *CodeGeeXChatModelLite = "codegeex-4"; +static const char *CodeGeeXChatModelPro = "codegeex-chat-pro"; class AiManagerPrivate; class AiManager : public QObject @@ -25,8 +28,10 @@ class AiManager : public QObject void appendModel(const LLMInfo &LLMInfo); void removeModel(const LLMInfo &LLMInfo); bool checkModelValid(const LLMInfo &info, QString *errStr); + QList getDefaultLLM(); void readLLMFromOption(); + void initCompleteProvider(); private: AiManager(QObject *parent = nullptr); diff --git a/src/plugins/aimanager/aimanager.qrc b/src/plugins/aimanager/aimanager.qrc index 81446e4bc..1bf182b45 100644 --- a/src/plugins/aimanager/aimanager.qrc +++ b/src/plugins/aimanager/aimanager.qrc @@ -1,4 +1,6 @@ - + + builtin/icons/codegeex_model_lite_16px.svg + builtin/icons/codegeex_model_pro_16px.svg diff --git a/src/plugins/aimanager/aiplugin.cpp b/src/plugins/aimanager/aiplugin.cpp index 0614b0c85..d2aa820c7 100644 --- a/src/plugins/aimanager/aiplugin.cpp +++ b/src/plugins/aimanager/aiplugin.cpp @@ -26,11 +26,24 @@ bool AiPlugin::start() using namespace std::placeholders; aiService->getAllModel = std::bind(&AiManager::getAllModel, impl); aiService->getLLM = std::bind(&AiManager::getLLM, impl, _1); + aiService->getCodeGeeXLLMLite = []() -> LLMInfo { + auto defaultLLMs = AiManager::instance()->getDefaultLLM(); + for (auto llm : defaultLLMs) { + if (llm.modelName == CodeGeeXChatModelLite) + return llm; + } + }; + aiService->getCodeGeeXLLMPro = []() -> LLMInfo { + auto defaultLLMs = AiManager::instance()->getDefaultLLM(); + for (auto llm : defaultLLMs) { + if (llm.modelName == CodeGeeXChatModelPro) + return llm; + } + }; auto optionService = dpfGetService(dpfservice::OptionService); if (optionService) { -// TODO:uncomment the code when everything is ok -// optionService->implGenerator(option::GROUP_AI, OptionCustomModelsGenerator::kitName()); + optionService->implGenerator(option::GROUP_AI, OptionCustomModelsGenerator::kitName()); } return true; diff --git a/src/plugins/codegeex/builtin/icons/codegeex_model_lite_16px.svg b/src/plugins/aimanager/builtin/icons/codegeex_model_lite_16px.svg similarity index 100% rename from src/plugins/codegeex/builtin/icons/codegeex_model_lite_16px.svg rename to src/plugins/aimanager/builtin/icons/codegeex_model_lite_16px.svg diff --git a/src/plugins/codegeex/builtin/icons/codegeex_model_pro_16px.svg b/src/plugins/aimanager/builtin/icons/codegeex_model_pro_16px.svg similarity index 100% rename from src/plugins/codegeex/builtin/icons/codegeex_model_pro_16px.svg rename to src/plugins/aimanager/builtin/icons/codegeex_model_pro_16px.svg diff --git a/src/plugins/aimanager/codegeex/codegeexcompletionprovider.cpp b/src/plugins/aimanager/codegeex/codegeexcompletionprovider.cpp new file mode 100644 index 000000000..4e6e8a3de --- /dev/null +++ b/src/plugins/aimanager/codegeex/codegeexcompletionprovider.cpp @@ -0,0 +1,172 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "codegeexcompletionprovider.h" +#include "codegeexllm.h" + +bool responseValid(const QString &response) +{ + bool valid = !(response.isEmpty() + || response.startsWith("\n\n\n") + || response.startsWith("\n \n ")); + if (!valid) { + qWarning() << "Reponse not valid: " << response; + } + return valid; +} + +CodeGeeXCompletionProvider::CodeGeeXCompletionProvider(QObject *parent) + : AbstractInlineCompletionProvider(parent) +{ + completeLLM = new CodeGeeXLLM(this); + completeLLM->setStream(false); + connect(completeLLM, &AbstractLLM::dataReceived, this, &CodeGeeXCompletionProvider::handleDataReceived); + + timer.setSingleShot(true); + timer.setInterval(500); +} + +QString CodeGeeXCompletionProvider::providerName() const +{ + return "CodeGeeX"; +} + +void CodeGeeXCompletionProvider::provideInlineCompletionItems(const Position &pos, const InlineCompletionContext &c) +{ + for (const auto &item : qAsConst(completionItems)) { + if (c.prefix.endsWith(item.completion) && !item.completion.isEmpty()) { + return; + } + } + + if (completeLLM->modelState() == AbstractLLM::Busy) + completeLLM->cancel(); + positon = pos; + context = c; + connect(&timer, &QTimer::timeout, this, &CodeGeeXCompletionProvider::postGenerate, Qt::UniqueConnection); + timer.start(); +} + +QList CodeGeeXCompletionProvider::inlineCompletionItems() const +{ + return completionItems; +} + +void CodeGeeXCompletionProvider::setInlineCompletionEnabled(bool enabled) +{ + if (!enabled && timer.isActive()) + timer.stop(); + + completionEnabled = enabled; +} + +void CodeGeeXCompletionProvider::setInlineCompletions(const QStringList &completions) +{ + completionItems.clear(); + for (const auto &completion : completions) { + InlineCompletionItem item { completion, positon }; + completionItems << item; + } +} + +bool CodeGeeXCompletionProvider::inlineCompletionEnabled() const +{ + return completionEnabled; +} + +void CodeGeeXCompletionProvider::accepted() +{ + completionItems.clear(); +} + +void CodeGeeXCompletionProvider::rejected() +{ + completionItems.clear(); +} + +AbstractInlineCompletionProvider::InlineCompletionContext CodeGeeXCompletionProvider::inlineCompletionContext() const +{ + return context; +} + +void CodeGeeXCompletionProvider::postGenerate() +{ + if (!inlineCompletionEnabled()) + return; + + const auto &context = inlineCompletionContext(); + if (!context.prefix.endsWith(generatedCode) || generateCache.isEmpty()) { + generateType = checkPrefixType(context.prefix); + completeLLM->generate(context.prefix, context.suffix); + } else { + generatedCode = extractSingleLine(); + setInlineCompletions({ generatedCode }); + emit finished(); + } +} + +QString CodeGeeXCompletionProvider::extractSingleLine() +{ + if (generateCache.isEmpty()) + return ""; + + bool extractedCode = false; + QString completion = ""; + for (auto line : generateCache) { + if (extractedCode) + break; + if (line != "") + extractedCode = true; + + completion += line == "" ? "\n" : line; + generateCache.removeFirst(); + } + completion += "\n"; + + //check if left cache all '\n' + bool leftAllEmpty = true; + for (auto line : generateCache) { + if (line == "") + continue; + leftAllEmpty = false; + break; + } + if (leftAllEmpty) { + generateCache.clear(); + completion += "\n"; + } + + if (!extractedCode) + completion = ""; + return completion; +} + +CodeGeeXCompletionProvider::GenerateType CodeGeeXCompletionProvider::checkPrefixType(const QString &prefixCode) +{ + //todo + Q_UNUSED(prefixCode) + if (0) + return CodeGeeXCompletionProvider::Line; + else + return CodeGeeXCompletionProvider::Block; +} + +void CodeGeeXCompletionProvider::handleDataReceived(const QString &data, AbstractLLM::ResponseState state) +{ + if (state == AbstractLLM::Failed || state == AbstractLLM::Canceled) + return; + QString completion = ""; + if (generateType == Line) { + generateCache = data.split('\n'); + completion = extractSingleLine(); + } else if (generateType == Block) { + generateCache.clear(); + completion = data; + } + if (completion.endsWith('\n')) + completion.chop(1); + generatedCode = completion; + setInlineCompletions({ completion }); + emit finished(); +} diff --git a/src/plugins/codegeex/codegeex/codegeexcompletionprovider.h b/src/plugins/aimanager/codegeex/codegeexcompletionprovider.h similarity index 72% rename from src/plugins/codegeex/codegeex/codegeexcompletionprovider.h rename to src/plugins/aimanager/codegeex/codegeexcompletionprovider.h index ac5d67312..73da9b5b8 100644 --- a/src/plugins/codegeex/codegeex/codegeexcompletionprovider.h +++ b/src/plugins/aimanager/codegeex/codegeexcompletionprovider.h @@ -6,14 +6,19 @@ #define CODEGEEXCOMPLETIONPROVIDER_H #include "base/abstractinlinecompletionprovider.h" +#include "base/ai/abstractllm.h" #include -namespace CodeGeeX { class CodeGeeXCompletionProvider : public AbstractInlineCompletionProvider { Q_OBJECT public: + enum GenerateType { + Line, + Block + }; + explicit CodeGeeXCompletionProvider(QObject *parent = nullptr); QString providerName() const override; @@ -28,12 +33,21 @@ class CodeGeeXCompletionProvider : public AbstractInlineCompletionProvider void setInlineCompletions(const QStringList &completions); private: + void postGenerate(); Position positon; InlineCompletionContext context; QList completionItems; QAtomicInteger completionEnabled { false }; + void handleDataReceived(const QString &data, AbstractLLM::ResponseState state); QTimer timer; + + QStringList generateCache {}; + QString generatedCode {}; + QString extractSingleLine(); + + AbstractLLM *completeLLM { nullptr }; + GenerateType generateType; + GenerateType checkPrefixType(const QString &prefixCode); }; -} // namespace CodeGeeX #endif // CODEGEEXCOMPLETIONPROVIDER_H diff --git a/src/plugins/aimanager/codegeex/codegeexconversation.cpp b/src/plugins/aimanager/codegeex/codegeexconversation.cpp new file mode 100644 index 000000000..ea3d84ed6 --- /dev/null +++ b/src/plugins/aimanager/codegeex/codegeexconversation.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "codegeexconversation.h" + +#include +#include + +CodeGeeXConversation::CodeGeeXConversation() +{ +} diff --git a/src/plugins/aimanager/codegeex/codegeexconversation.h b/src/plugins/aimanager/codegeex/codegeexconversation.h new file mode 100644 index 000000000..133d78ff4 --- /dev/null +++ b/src/plugins/aimanager/codegeex/codegeexconversation.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef CODEGEEXCONVERSATION_H +#define CODEGEEXCONVERSATION_H + +#include + +#include + +class CodeGeeXConversation : public Conversation +{ +public: + explicit CodeGeeXConversation(); +}; + +#endif // CODEGEEXCONVERSATION_H diff --git a/src/plugins/aimanager/codegeex/codegeexllm.cpp b/src/plugins/aimanager/codegeex/codegeexllm.cpp new file mode 100644 index 000000000..13d74d571 --- /dev/null +++ b/src/plugins/aimanager/codegeex/codegeexllm.cpp @@ -0,0 +1,571 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "codegeexllm.h" +#include "codegeexconversation.h" +#include "services/option/optionmanager.h" +#include "services/window/windowservice.h" +#include "services/editor/editorservice.h" +#include "services/project/projectservice.h" + +#include +#include +#include +#include +#include + +using namespace dpfservice; + +static int kCode_Success = 200; +static const char *kUrlGenerateMultiLine = "https://api.codegeex.cn:8443/v3/completions/inline?stream=false"; +static const char *kUrlCreateNewSession = "https://codegeex.cn/prod/code/chatGlmTalk/insert"; +static const char *kUrlQueryUserInfo = "https://codegeex.cn/prod/code/oauth/getUserInfo"; + +QString uuid() +{ + QUuid uuid = QUuid::createUuid(); + return uuid.toString().replace("{", "").replace("}", "").replace("-", ""); +} + +QPair getCurrentFileInfo() +{ + EditorService *editorService = dpfGetService(EditorService); + auto filePath = editorService->currentFile(); + QString fileName; + if (QFileInfo(filePath).exists()) + fileName = QFileInfo(filePath).fileName(); + else + fileName = "main.cpp"; + auto fileType = support_file::Language::id(filePath); + auto fileLang = support_file::Language::idAlias(fileType); + + // The above LANGUAGE class supports fewer file languages, and unknown file languages are temporarily represented by suffix. + if (fileLang.isEmpty()) + fileLang = QFileInfo(filePath).suffix(); + return qMakePair(fileName, fileLang); +} + +class CodeGeeXLLMPrivate +{ +public: + CodeGeeXLLMPrivate(CodeGeeXLLM *qq); + ~CodeGeeXLLMPrivate(); + + QNetworkReply *postMessage(const QString &url, const QString &apiKey, const QByteArray &body); + QNetworkReply *getMessage(const QString &url, const QString &apiKey); + + void login(); + void saveConfig(const QString &sessionId); + void loadConfig(); + Entry processJsonObject(const QString &event, QJsonObject *obj); + void createNewSession(); + void handleReplyFinished(QNetworkReply *reply); + void handleStreamResponse(const QByteArray &data, AbstractLLM::ResponseHandler handler = nullptr); + void handleNonStreamResponse(const QByteArray &data, AbstractLLM::ResponseHandler handler = nullptr); + void replyMessage(const QString &data, AbstractLLM::ResponseState state, AbstractLLM::ResponseHandler handler); + void processResponse(QNetworkReply *reply, AbstractLLM::ResponseHandler handler = nullptr); + + QString modelName { "" }; + QString modelPath { "" }; + QString apiKey { "" }; // == session id + QString talkId { "" }; + QString locale { "zh" }; + double temprature { 1.0 }; + int maxTokens = 0; // default not set + bool stream { true }; + + CodeGeeXConversation *c; + + QNetworkAccessManager *manager = nullptr; + CodeGeeXLLM *q = nullptr; +}; + +CodeGeeXLLMPrivate::CodeGeeXLLMPrivate(CodeGeeXLLM *qq) + : q(qq) +{ + c = new CodeGeeXConversation; + manager = new QNetworkAccessManager(qq); + loadConfig(); +} + +CodeGeeXLLMPrivate::~CodeGeeXLLMPrivate() +{ + delete c; +} + +QNetworkReply *CodeGeeXLLMPrivate::postMessage(const QString &url, const QString &apiKey, const QByteArray &body) +{ + QNetworkRequest request; + request.setUrl(QUrl(url)); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("code-token", apiKey.toUtf8()); + + if (QThread::currentThread() != qApp->thread()) { + QNetworkAccessManager* threadManager(new QNetworkAccessManager); + CodeGeeXLLM::connect(QThread::currentThread(), &QThread::finished, threadManager, &QNetworkAccessManager::deleteLater); + return threadManager->post(request, body); + } + return manager->post(request, body); +} + +QNetworkReply *CodeGeeXLLMPrivate::getMessage(const QString &url, const QString &apiKey) +{ + QNetworkRequest request; + request.setUrl(QUrl(url)); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("code-token", apiKey.toUtf8()); + + if (QThread::currentThread() != qApp->thread()) { + QNetworkAccessManager* threadManager(new QNetworkAccessManager); + CodeGeeXLLM::connect(QThread::currentThread(), &QThread::finished, threadManager, &QNetworkAccessManager::deleteLater); + return threadManager->get(request); + } + return manager->get(request); +} + +void CodeGeeXLLMPrivate::login() +{ + apiKey = uuid(); + QString machineId = QSysInfo::machineUniqueId(); + + QString url = QString("https://codegeex.cn/auth?sessionId=%1&%2=%3&device=%4").arg(apiKey).arg(machineId).arg(apiKey).arg("deepin-unioncode"); + QDesktopServices::openUrl(QUrl(url)); + saveConfig(apiKey); +} + +void CodeGeeXLLMPrivate::saveConfig(const QString &sessionId) +{ + QVariantMap map {{ "sessionId", sessionId }}; + + OptionManager::getInstance()->setValue("CodeGeeX", "Id", map); +} + +void CodeGeeXLLMPrivate::loadConfig() +{ + const auto map = OptionManager::getInstance()->getValue("CodeGeeX", "Id").toMap(); + if (map.isEmpty()) + return; + + apiKey = map.value("sessionId").toString(); +} + +void CodeGeeXLLMPrivate::createNewSession() +{ + loadConfig(); // incase sessionId has updated + QString url = kUrlCreateNewSession; + QString currentMSecsStr = QString::number(QDateTime::currentMSecsSinceEpoch()); + QString sessionTitle("Session_" + currentMSecsStr); + QString taskId(uuid()); + + QJsonObject jsonObject; + jsonObject.insert("prompt", sessionTitle); + jsonObject.insert("talkId", taskId); + + QNetworkReply *reply = postMessage(url, apiKey, QJsonDocument(jsonObject).toJson()); + QEventLoop loop; + CodeGeeXLLM::connect(reply, &QNetworkReply::finished, q, [=, &loop]() { + if (reply->error()) { + qCritical() << "CodeGeeX Session created faield \nError:" << reply->errorString(); + loop.exit(); + return; + } + + QString response = QString::fromUtf8(reply->readAll()); + QJsonDocument document = QJsonDocument::fromJson(response.toUtf8()); + QJsonObject jsonObject = document.object(); + int code = jsonObject["code"].toInt(); + if (code == kCode_Success) + talkId = taskId; + loop.exit(); + }); + loop.exec(); +} + +void CodeGeeXLLMPrivate::handleReplyFinished(QNetworkReply *reply) +{ + if (q->modelState() == AbstractLLM::Idle) // llm is alread stopped + return; + if (reply->error()) { + qWarning() << "NetWork Error: " << reply->errorString(); + emit q->dataReceived(reply->errorString(), AbstractLLM::ResponseState::Failed); + } + q->setModelState(AbstractLLM::Idle); +} + +void CodeGeeXLLMPrivate::handleStreamResponse(const QByteArray &data, AbstractLLM::ResponseHandler handler) +{ + QString replyMsg = QString::fromUtf8(data); + QStringList lines = replyMsg.split('\n'); + QString event; + QString id; + + for (const auto &line : lines) { + auto index = line.indexOf(':'); + auto key = line.mid(0, index); + auto value = line.mid(index + 1); + + if (key == "event") { + event = value.trimmed(); + } else if (key == "id") { + id = value.trimmed(); + } else if (key == "data") { + QJsonParseError error; + QJsonDocument jsonDocument = QJsonDocument::fromJson(value.toUtf8(), &error); + + QJsonObject jsonObject = jsonDocument.object(); + auto entry = processJsonObject(event, &jsonObject); + if (error.error != QJsonParseError::NoError) { + qCritical() << "JSON parse error: " << error.errorString(); + if (event == "finish") { + replyMessage(entry.text, AbstractLLM::Failed, handler); + return; + } + continue; + } + + if (entry.type == "crawl") + emit q->customDataReceived("crawl", entry.websites); + + if (event == "add") + replyMessage(entry.text, AbstractLLM::Receiving, handler); + else if (event == "finish") { + c->addResponse(entry.text); + replyMessage(entry.text, AbstractLLM::Success, handler); + } + } + } +} + +void CodeGeeXLLMPrivate::handleNonStreamResponse(const QByteArray &data, AbstractLLM::ResponseHandler handler) +{ + QString replyMsg = QString::fromUtf8(data); + QJsonParseError error; + QJsonDocument jsonDocument = QJsonDocument::fromJson(replyMsg.toUtf8(), &error); + if (error.error != QJsonParseError::NoError) { + qCritical() << "JSON parse error: " << error.errorString(); + replyMessage(error.errorString(), AbstractLLM::Failed, handler); + return; + } + QJsonObject jsonObject = jsonDocument.object(); + if (!jsonObject.value("inline_completions").isUndefined()) { + auto content = jsonObject.value("inline_completions").toArray().at(0).toObject(); + QString code = content.value("text").toString(); + if (content.value("finish_reason").toString() == "length") { + // Due to the length limit of the code, the last line will be discarded when the code is truncated. + auto codeLines = code.split('\n'); + if (codeLines.size() > 1) + codeLines.removeLast(); + code = codeLines.join('\n'); + } + + replyMessage(code, AbstractLLM::Success, handler); + return; + } + + auto response = jsonObject.value("text").toString(); + replyMessage(response, AbstractLLM::Success, handler); +} + +Entry CodeGeeXLLMPrivate::processJsonObject(const QString &event, QJsonObject *obj) +{ + Entry entry; + if (!obj || obj->isEmpty()) + return entry; + + if (event == "add") { + entry.type = "text"; + entry.text = obj->value("text").toString(); + return entry; + } + + if (event == "processing") { + auto type = obj->value("type").toString(); + entry.type = type; + if (type == "keyword") { + auto keyWords = obj->value("data").toArray(); + QString keys; + for (auto key : keyWords) + keys = keys + key.toString() + " "; + entry.text = keys.trimmed(); + } else if (type == "crawl") { + auto crawlObj = obj->value("data").toObject(); + entry.websites = crawlObj; + } + return entry; + } + + if (event == "finish") { + entry.text = obj->value("text").toString(); + entry.type = event; + } + + return entry; +} + +void CodeGeeXLLMPrivate::replyMessage(const QString &data, AbstractLLM::ResponseState state, AbstractLLM::ResponseHandler handler) +{ + if (handler) + handler(data, state); + else + emit q->dataReceived(data, state); +} + +void CodeGeeXLLMPrivate::processResponse(QNetworkReply *reply, AbstractLLM::ResponseHandler handler) +{ + CodeGeeXLLM::connect(reply, &QNetworkReply::readyRead, q, [=]() { + if (reply->error()) { + qCritical() << "Error:" << reply->errorString(); + replyMessage(reply->errorString(), AbstractLLM::Failed, handler); + } else { + if (stream) + handleStreamResponse(reply->readAll(), handler); + else + handleNonStreamResponse(reply->readAll(), handler); + } + }); +} + +CodeGeeXLLM::CodeGeeXLLM(QObject *parent) + : AbstractLLM(parent), d(new CodeGeeXLLMPrivate(this)) +{ +} + +CodeGeeXLLM::~CodeGeeXLLM() +{ + delete d; +} + +QString CodeGeeXLLM::modelName() const +{ + return d->modelName; +} + +QString CodeGeeXLLM::modelPath() const +{ + return d->modelPath; +} + +Conversation *CodeGeeXLLM::getCurrentConversation() +{ + return d->c; +} + +void CodeGeeXLLM::setModelName(const QString &name) +{ + d->modelName = name; +} + +void CodeGeeXLLM::setModelPath(const QString &path) +{ + d->modelPath = path; +} + +void CodeGeeXLLM::setApiKey(const QString &key) +{ + d->apiKey = key; +} + +bool CodeGeeXLLM::checkValid(QString *errStr) +{ + d->loadConfig(); + QString url = kUrlQueryUserInfo; + QNetworkReply *reply = d->getMessage(url, d->apiKey); + QEventLoop loop; + bool valid = false; + connect(reply, &QNetworkReply::finished, this, [=, &loop, &valid]() { + if (reply->error()) { + qCritical() << "Error:" << reply->errorString(); + loop.exit(); + return; + } + QString response = QString::fromUtf8(reply->readAll()); + QJsonDocument document = QJsonDocument::fromJson(response.toUtf8()); + QJsonObject jsonObject = document.object(); + int code = jsonObject["code"].toInt(); + if (code == kCode_Success) { + valid = true; + } else { + valid = false; + *errStr = "Please Login CodeGeeX first"; + + QStringList actions { "codegeex_login_default", tr("Login") }; + auto windowService = dpfGetService(WindowService); + windowService->notifyWithCallback(0, "CodeGeeX", tr("Please login to use CodeGeeX."), actions, [=](const QString &actId) { + if (actId == "codegeex_login_default") + d->login(); + }); + } + loop.exit(); + }); + loop.exec(); + + return valid; +} + +QJsonObject CodeGeeXLLM::create(const Conversation &conversation) +{ + QJsonObject dataObject; + dataObject.insert("ide", qApp->applicationName()); + + if (d->talkId.isEmpty()) + d->createNewSession(); + + const QJsonArray &array = conversation.getConversions(); + QString prompt = ""; + if (!array.isEmpty() && array.last()["role"] == "user") { + prompt = array.last()["content"].toString(); + } + QJsonArray history {}; // [{user}, {assistant} {..}] + for (int i = 0; i < array.size() - 1; i++) { + QJsonObject obj; + if (array[i]["role"] == "user" && array[i+1]["role"] == "assistant") { + obj.insert("query", array[i]["content"].toString()); + obj.insert("answer", array[i+1]["content"].toString()); + } + history.append(obj); + } + + dataObject.insert("prompt", prompt); + dataObject.insert("machineId", QString(QSysInfo::machineUniqueId())); + dataObject.insert("history", history); + dataObject.insert("locale", d->locale); + dataObject.insert("model", d->modelName); + dataObject.insert("stream", d->stream); + dataObject.insert("talkId", d->talkId); + + return dataObject; +} + +void CodeGeeXLLM::request(const QJsonObject &data) +{ + QByteArray body = QJsonDocument(data).toJson(); + setModelState(Busy); + + QNetworkReply *reply = d->postMessage(modelPath(), d->apiKey, body); + connect(this, &CodeGeeXLLM::requstCancel, reply, &QNetworkReply::abort); + connect(reply, &QNetworkReply::finished, this, [=](){ + d->handleReplyFinished(reply); + }); + + d->processResponse(reply); +} + +void CodeGeeXLLM::request(const QString &prompt, ResponseHandler handler) +{ + if (d->talkId.isEmpty()) + d->createNewSession(); + setModelState(Busy); + + QJsonObject dataObject; + dataObject.insert("ide", qApp->applicationName()); + dataObject.insert("prompt", prompt); + dataObject.insert("machineId", QString(QSysInfo::machineUniqueId())); + dataObject.insert("locale", d->locale); + dataObject.insert("model", d->modelName); + dataObject.insert("stream", d->stream); + dataObject.insert("talkId", d->talkId); + + QNetworkReply *reply = d->postMessage(modelPath(), d->apiKey, QJsonDocument(dataObject).toJson()); + connect(this, &CodeGeeXLLM::requstCancel, reply, &QNetworkReply::abort); + connect(reply, &QNetworkReply::finished, this, [=](){ + d->handleReplyFinished(reply); + }); + + d->processResponse(reply, handler); +} + +/* + data = { + "context": [{ + "kind": + "active_document":{} + }, + { + "kind": + "document":{} + }, + ], + "model":, + "lang": + } +*/ +void CodeGeeXLLM::generate(const QString &prefix, const QString &suffix) +{ + if (d->talkId.isEmpty()) + d->createNewSession(); + setModelState(Busy); + + auto file = getCurrentFileInfo(); + + QJsonObject activeDocument; + activeDocument.insert("path", file.first); + activeDocument.insert("prefix", prefix); + activeDocument.insert("suffix", suffix); + activeDocument.insert("lang", file.second); + + QJsonObject activeContextItem; + activeContextItem.insert("kind", "active_document"); + activeContextItem.insert("active_document", activeDocument); + +// ProjectService *prjSrv = dpfGetService(ProjectService); + QJsonArray context; + context.append(activeContextItem); +// QJsonObject queryResults = CodeGeeXManager::instance()->query(prjSrv->getActiveProjectInfo().workspaceFolder(), prefix, 5); +// QJsonArray chunks = queryResults["Chunks"].toArray(); + +// for (auto chunk : chunks) { +// QJsonObject document; +// document.insert("path", chunk.toObject()["fileName"].toString()); +// document.insert("text", chunk.toObject()["content"].toString()); +// document.insert("lang", file.second); + +// QJsonObject contextItem; +// contextItem.insert("kind", "document"); +// contextItem.insert("document", document); +// context.append(contextItem); +// } + + QJsonObject json; + json.insert("ide", qApp->applicationName()); + json.insert("ide_version", version()); + json.insert("context", context); + json.insert("model", modelName()); + json.insert("lang", file.second); + json.insert("max_new_tokens", d->maxTokens == 0 ? 126 : d->maxTokens); + + QNetworkReply *reply = d->postMessage(kUrlGenerateMultiLine, d->apiKey, QJsonDocument(json).toJson()); + connect(this, &CodeGeeXLLM::requstCancel, reply, &QNetworkReply::abort); + connect(reply, &QNetworkReply::finished, this, [=](){ + d->handleReplyFinished(reply); + }); + + d->processResponse(reply); +} + +void CodeGeeXLLM::setTemperature(double temperature) +{ + d->temprature = temperature; +} + +void CodeGeeXLLM::setStream(bool isStream) +{ + d->stream = isStream; +} + +void CodeGeeXLLM::setLocale(Locale lc) +{ + d->locale = lc == Locale::Zh ? "zh" : "en"; +} + +void CodeGeeXLLM::cancel() +{ + setModelState(Idle); + emit requstCancel(); + emit dataReceived("", AbstractLLM::ResponseState::Canceled); +} + +void CodeGeeXLLM::setMaxTokens(int maxTokens) +{ + d->maxTokens = maxTokens; +} diff --git a/src/plugins/aimanager/codegeex/codegeexllm.h b/src/plugins/aimanager/codegeex/codegeexllm.h new file mode 100644 index 000000000..23013d858 --- /dev/null +++ b/src/plugins/aimanager/codegeex/codegeexllm.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#ifndef CodeGeeXLLM_H +#define CodeGeeXLLM_H + +struct Entry +{ + QString type; + QString text; + QJsonObject websites; +}; + +class CodeGeeXLLMPrivate; +class CodeGeeXLLM : public AbstractLLM +{ + Q_OBJECT +public: + explicit CodeGeeXLLM(QObject *parent = nullptr); + ~CodeGeeXLLM() override; + + Conversation* getCurrentConversation() override; + void setModelName(const QString &modelName); + void setModelPath(const QString &path); + void setApiKey(const QString &apiKey); + + QString modelName() const override; + QString modelPath() const override; + bool checkValid(QString *errStr) override; + QJsonObject create(const Conversation &conversation) override; + void request(const QJsonObject &data) override; + void request(const QString &prompt, ResponseHandler handler = nullptr) override; + void generate(const QString &prefix, const QString &suffix) override; + void setTemperature(double temperature) override; + void setStream(bool isStream) override; + void setLocale(Locale lc) override; + void cancel() override; + void setMaxTokens(int maxTokens) override; + +signals: + void requstCancel(); + +private: + CodeGeeXLLMPrivate *d; +}; + +#endif // CodeGeeXLLM_H diff --git a/src/plugins/aimanager/openai/openaicompatiblellm.cpp b/src/plugins/aimanager/openai/openaicompatiblellm.cpp index 9cb510b9c..68a54e39d 100644 --- a/src/plugins/aimanager/openai/openaicompatiblellm.cpp +++ b/src/plugins/aimanager/openai/openaicompatiblellm.cpp @@ -31,8 +31,10 @@ QJsonObject parseNonStreamContent(const QByteArray &data) QString text = choice.toObject()["text"].toString(); parseResult["content"] = text; } - if (choice.toObject().contains("finish_reason")) - parseResult["finish_reason"] = root["finish_reason"].toString(); + if (choice.toObject().contains("finish_reason")) { + QString reason = choice.toObject()["finish_reason"].toString(); + parseResult["finish_reason"] = reason; + } } } else if (root.contains("response")) { QString response = root["response"].toString(); @@ -49,6 +51,9 @@ class OpenAiCompatibleLLMPrivate QNetworkReply *postMessage(const QString &url, const QString &apiKey, const QByteArray &body); QNetworkReply *getMessage(const QString &url, const QString &apiKey); + void replyMessage(const QString &data, AbstractLLM::ResponseState state, AbstractLLM::ResponseHandler handler); + void processResponse(QNetworkReply *reply, AbstractLLM::ResponseHandler handler = nullptr); + void handleReplyFinished(QNetworkReply *reply); QString modelName { "" }; QString modelPath { "" }; @@ -58,7 +63,6 @@ class OpenAiCompatibleLLMPrivate bool stream { true }; QByteArray httpResult {}; - bool waitingResponse { false }; OpenAiCompatibleConversation *currentConversation = nullptr; QNetworkAccessManager *manager = nullptr; @@ -108,6 +112,66 @@ QNetworkReply *OpenAiCompatibleLLMPrivate::getMessage(const QString &url, const return manager->get(request); } +void OpenAiCompatibleLLMPrivate::replyMessage(const QString &data, AbstractLLM::ResponseState state, AbstractLLM::ResponseHandler handler) +{ + if (handler) + handler(data, state); + else + emit q->dataReceived(data, state); +} + +void OpenAiCompatibleLLMPrivate::processResponse(QNetworkReply *reply, AbstractLLM::ResponseHandler handler) +{ + OpenAiCompatibleLLM::connect(reply, &QNetworkReply::readyRead, q, [=]() { + if (reply->error()) { + qCritical() << "Error:" << reply->errorString(); + replyMessage(reply->errorString(), AbstractLLM::ResponseState::Failed, handler); + } else { + auto data = reply->readAll(); + + // process {"code":,"msg":,"success":false} + QJsonDocument jsonDoc = QJsonDocument::fromJson(data); + if (!jsonDoc.isNull()) { + QJsonObject jsonObj = jsonDoc.object(); + if (jsonObj.contains("success") && !jsonObj.value("success").toBool()) { + replyMessage(jsonObj.value("msg").toString(), AbstractLLM::Failed, handler); + return; + } + } + + httpResult.append(data); + QString content; + QJsonObject retJson; + if (stream) { + retJson = OpenAiCompatibleConversation::parseContentString(QString(data)); + if (retJson.contains("content")) + content = retJson.value("content").toString(); + } else { + retJson = parseNonStreamContent(data); + content = retJson["content"].toString(); + } + + if (retJson["finish_reason"].toString() == "length") + replyMessage(content, AbstractLLM::ResponseState::CutByLength, handler); + else if (retJson["finish_reason"].toString() == "stop") + replyMessage(content, AbstractLLM::Success, handler); + else + replyMessage(content, AbstractLLM::Receiving, handler); + } + }); +} + +void OpenAiCompatibleLLMPrivate::handleReplyFinished(QNetworkReply *reply) +{ + if (q->modelState() == AbstractLLM::Idle) // llm is alread stopped + return; + if (reply->error()) { + qWarning() << "NetWork Error: " << reply->errorString(); + emit q->dataReceived(reply->errorString(), AbstractLLM::ResponseState::Failed); + } + q->setModelState(AbstractLLM::Idle); +} + OpenAiCompatibleLLM::OpenAiCompatibleLLM(QObject *parent) : AbstractLLM(parent), d(new OpenAiCompatibleLLMPrivate(this)) { @@ -165,19 +229,18 @@ bool OpenAiCompatibleLLM::checkValid(QString *errStr) c.addUserData("Testing. Just say hi and nothing else"); auto obj = create(c); - request(obj); QEventLoop loop; bool valid = false; QString errstr; - connect(this, &AbstractLLM::dataReceived, &loop, [&, this](const QString & data, ResponseState state){ - if (state == ResponseState::Receiving) - return; - - if (state == ResponseState::Success) { + QByteArray body = QJsonDocument(obj).toJson(); + QNetworkReply *reply = d->postMessage(modelPath() + "/chat/completions", d->apiKey, body); + connect(reply, &QNetworkReply::finished, &loop, [&, this](){ + if (reply->error()) { + *errStr = reply->errorString(); + valid = false; + } else { valid = true; - } else if (errStr != nullptr){ - *errStr = data; } loop.quit(); }); @@ -201,38 +264,25 @@ QJsonObject OpenAiCompatibleLLM::create(const Conversation &conversation) void OpenAiCompatibleLLM::request(const QJsonObject &data) { - if (d->waitingResponse) - return; - + setModelState(Busy); QByteArray body = QJsonDocument(data).toJson(); d->httpResult.clear(); - d->waitingResponse = true; d->currentConversation->update(body); - QNetworkReply *reply = d->postMessage(modelPath() + "/v1/chat/completions", d->apiKey, body); + QNetworkReply *reply = d->postMessage(modelPath() + "/chat/completions", d->apiKey, body); connect(this, &OpenAiCompatibleLLM::requstCancel, reply, &QNetworkReply::abort); connect(reply, &QNetworkReply::finished, this, [=](){ - d->waitingResponse = false; if (!d->httpResult.isEmpty()) d->currentConversation->update(d->httpResult); - if (reply->error()) { - qWarning() << "NetWork Error: " << reply->errorString(); - emit dataReceived(reply->errorString(), AbstractLLM::ResponseState::Failed); - return; - } - emit dataReceived("", AbstractLLM::ResponseState::Success); + d->handleReplyFinished(reply); }); - processResponse(reply); + d->processResponse(reply); } -void OpenAiCompatibleLLM::request(const QString &prompt) +void OpenAiCompatibleLLM::request(const QString &prompt, ResponseHandler handler) { - if (d->waitingResponse) - return; - - d->waitingResponse = true; - + setModelState(Busy); QJsonObject dataObject; dataObject.insert("model", d->modelName); dataObject.insert("prompt", prompt); @@ -241,50 +291,36 @@ void OpenAiCompatibleLLM::request(const QString &prompt) if (d->maxTokens != 0) dataObject.insert("max_tokens", d->maxTokens); - QNetworkReply *reply = d->postMessage(modelPath() + "/v1/completions", d->apiKey, QJsonDocument(dataObject).toJson()); + QNetworkReply *reply = d->postMessage(modelPath() + "/completions", d->apiKey, QJsonDocument(dataObject).toJson()); connect(this, &OpenAiCompatibleLLM::requstCancel, reply, &QNetworkReply::abort); connect(reply, &QNetworkReply::finished, this, [=](){ - d->waitingResponse = false; - if (reply->error()) { - qWarning() << "NetWork Error: " << reply->errorString(); - emit dataReceived(reply->errorString(), AbstractLLM::ResponseState::Failed); - return; - } - emit dataReceived("", AbstractLLM::ResponseState::Success); + d->handleReplyFinished(reply); }); - processResponse(reply); + d->processResponse(reply, handler); } -void OpenAiCompatibleLLM::generate(const QString &prompt, const QString &suffix) +void OpenAiCompatibleLLM::generate(const QString &prefix, const QString &suffix) { - if (d->waitingResponse) - return; - - d->waitingResponse = true; - + setModelState(Busy); QJsonObject dataObject; dataObject.insert("model", d->modelName); dataObject.insert("suffix", suffix); - dataObject.insert("prompt", prompt); + dataObject.insert("prompt", prefix); dataObject.insert("temperature", 0.01); dataObject.insert("stream", d->stream); if (d->maxTokens != 0) dataObject.insert("max_tokens", d->maxTokens); + else + dataObject.insert("max_tokens", 128); // quickly response - QNetworkReply *reply = d->postMessage(modelPath() + "/api/generate", d->apiKey, QJsonDocument(dataObject).toJson()); + QNetworkReply *reply = d->postMessage(modelPath() + "/completions", d->apiKey, QJsonDocument(dataObject).toJson()); connect(this, &OpenAiCompatibleLLM::requstCancel, reply, &QNetworkReply::abort); connect(reply, &QNetworkReply::finished, this, [=](){ - d->waitingResponse = false; - if (reply->error()) { - qWarning() << "NetWork Error: " << reply->errorString(); - emit dataReceived(reply->errorString(), AbstractLLM::ResponseState::Failed); - return; - } - emit dataReceived("", AbstractLLM::ResponseState::Success); + d->handleReplyFinished(reply); }); - processResponse(reply); + d->processResponse(reply); } void OpenAiCompatibleLLM::setTemperature(double temperature) @@ -297,47 +333,15 @@ void OpenAiCompatibleLLM::setStream(bool isStream) d->stream = isStream; } -void OpenAiCompatibleLLM::processResponse(QNetworkReply *reply) +void OpenAiCompatibleLLM::setLocale(Locale lc) { - connect(reply, &QNetworkReply::readyRead, this, [=]() { - if (reply->error()) { - qCritical() << "Error:" << reply->errorString(); - emit dataReceived(reply->errorString(), AbstractLLM::ResponseState::Failed); - } else { - auto data = reply->readAll(); - - // process {"code":,"msg":,"success":false} - QJsonDocument jsonDoc = QJsonDocument::fromJson(data); - if (!jsonDoc.isNull()) { - QJsonObject jsonObj = jsonDoc.object(); - if (jsonObj.contains("success") && !jsonObj.value("success").toBool()) { - emit dataReceived(jsonObj.value("msg").toString(), AbstractLLM::ResponseState::Failed); - return; - } - } - - d->httpResult.append(data); - QString content; - QJsonObject retJson; - if (d->stream) { - retJson = OpenAiCompatibleConversation::parseContentString(QString(data)); - if (retJson.contains("content")) - content = retJson.value("content").toString(); - } else { - retJson = parseNonStreamContent(data); - } - - if (retJson["finish_reason"].toString() == "length") - emit dataReceived(content, AbstractLLM::ResponseState::CutByLength); - else - emit dataReceived(retJson["content"].toString(), AbstractLLM::ResponseState::Receiving); - } - }); + //todo + Q_UNUSED(lc); } void OpenAiCompatibleLLM::cancel() { - d->waitingResponse = false; + setModelState(AbstractLLM::Idle); d->httpResult.clear(); emit requstCancel(); emit dataReceived("", AbstractLLM::ResponseState::Canceled); @@ -347,8 +351,3 @@ void OpenAiCompatibleLLM::setMaxTokens(int maxTokens) { d->maxTokens = maxTokens; } - -bool OpenAiCompatibleLLM::isIdle() -{ - return !d->waitingResponse; -} diff --git a/src/plugins/aimanager/openai/openaicompatiblellm.h b/src/plugins/aimanager/openai/openaicompatiblellm.h index b83becd38..cd0e2e52a 100644 --- a/src/plugins/aimanager/openai/openaicompatiblellm.h +++ b/src/plugins/aimanager/openai/openaicompatiblellm.h @@ -24,15 +24,14 @@ class OpenAiCompatibleLLM : public AbstractLLM QString modelPath() const override; bool checkValid(QString *errStr) override; QJsonObject create(const Conversation &conversation) override; - void request(const QJsonObject &data) override; // v1/chat/compltions - void request(const QString &prompt) override; // v1/completions - void generate(const QString &prompt, const QString &suffix) override; // api/generate + void request(const QJsonObject &data) override; // chat/compltions + void request(const QString &prompt, ResponseHandler handler = nullptr) override; + void generate(const QString &prefix, const QString &suffix) override; // api/generate void setTemperature(double temperature) override; void setStream(bool isStream) override; - void processResponse(QNetworkReply *reply) override; + void setLocale(Locale lc) override; void cancel() override; void setMaxTokens(int maxTokens) override; - bool isIdle() override; signals: void requstCancel(); diff --git a/src/plugins/aimanager/openai/openaicompletionprovider.cpp b/src/plugins/aimanager/openai/openaicompletionprovider.cpp new file mode 100644 index 000000000..5bf5b1b39 --- /dev/null +++ b/src/plugins/aimanager/openai/openaicompletionprovider.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "openaicompletionprovider.h" +#include "openaicompatiblellm.h" + +OpenAiCompletionProvider::OpenAiCompletionProvider(QObject *parent) + : AbstractInlineCompletionProvider(parent) +{ + timer.setSingleShot(true); + timer.setInterval(500); +} + +QString OpenAiCompletionProvider::providerName() const +{ + return "OpenAiCompatible"; +} + +void OpenAiCompletionProvider::provideInlineCompletionItems(const Position &pos, const InlineCompletionContext &c) +{ + for (const auto &item : qAsConst(completionItems)) { + if (c.prefix.endsWith(item.completion) && !item.completion.isEmpty()) { + return; + } + } + + if (completeLLM->modelState() == AbstractLLM::Busy) + completeLLM->cancel(); + positon = pos; + context = c; + connect(&timer, &QTimer::timeout, this, &OpenAiCompletionProvider::postGenerate, Qt::UniqueConnection); + timer.start(); +} + +QList OpenAiCompletionProvider::inlineCompletionItems() const +{ + return completionItems; +} + +void OpenAiCompletionProvider::setInlineCompletionEnabled(bool enabled) +{ + if (!enabled && timer.isActive()) + timer.stop(); + + completionEnabled = enabled; +} + +void OpenAiCompletionProvider::setInlineCompletions(const QStringList &completions) +{ + completionItems.clear(); + for (const auto &completion : completions) { + InlineCompletionItem item { completion, positon }; + completionItems << item; + } +} + +void OpenAiCompletionProvider::setLLM(AbstractLLM *llm) +{ + if (!llm) + return; + if (completeLLM) + disconnect(completeLLM, &AbstractLLM::dataReceived, nullptr, nullptr); + completeLLM = llm; + completeLLM->setStream(false); + connect(completeLLM, &AbstractLLM::dataReceived, this, &OpenAiCompletionProvider::onDataReceived); +} + +bool OpenAiCompletionProvider::inlineCompletionEnabled() const +{ + return completionEnabled; +} + +void OpenAiCompletionProvider::accepted() +{ + completionItems.clear(); +} + +void OpenAiCompletionProvider::rejected() +{ + completionItems.clear(); +} + +AbstractInlineCompletionProvider::InlineCompletionContext OpenAiCompletionProvider::inlineCompletionContext() const +{ + return context; +} + +void OpenAiCompletionProvider::onDataReceived(const QString &data, AbstractLLM::ResponseState state) +{ + if (state == AbstractLLM::Success || state == AbstractLLM::CutByLength) { + QString completion = ""; + completion = data; + if (completion.endsWith('\n')) + completion.chop(1); + setInlineCompletions({ completion }); + emit finished(); + } +} + +void OpenAiCompletionProvider::postGenerate() +{ + if (!inlineCompletionEnabled()) + return; + + const auto &context = inlineCompletionContext(); + completeLLM->generate(context.prefix, context.suffix); +} diff --git a/src/plugins/aimanager/openai/openaicompletionprovider.h b/src/plugins/aimanager/openai/openaicompletionprovider.h new file mode 100644 index 000000000..c34516586 --- /dev/null +++ b/src/plugins/aimanager/openai/openaicompletionprovider.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef OPENAICOMPLETIONPROVIDER_H +#define OPENAICOMPLETIONPROVIDER_H + +#include "base/abstractinlinecompletionprovider.h" +#include "base/ai/abstractllm.h" + +#include + +class LLMInfo; +class OpenAiCompletionProvider : public AbstractInlineCompletionProvider +{ + Q_OBJECT +public: + explicit OpenAiCompletionProvider(QObject *parent = nullptr); + + QString providerName() const override; + void provideInlineCompletionItems(const Position &pos, const InlineCompletionContext &c) override; + QList inlineCompletionItems() const override; + bool inlineCompletionEnabled() const override; + void accepted() override; + void rejected() override; + + InlineCompletionContext inlineCompletionContext() const; + void setInlineCompletionEnabled(bool enabled); + void setInlineCompletions(const QStringList &completions); + void setLLM(AbstractLLM *llm); + +private slots: + void onDataReceived(const QString &data, AbstractLLM::ResponseState state); + +private: + void postGenerate(); + Position positon; + InlineCompletionContext context; + QList completionItems; + QAtomicInteger completionEnabled { false }; + QTimer timer; + + AbstractLLM *completeLLM { nullptr }; +}; + +#endif // OPENAICOMPLETIONPROVIDER_H diff --git a/src/plugins/aimanager/option/detailwidget.cpp b/src/plugins/aimanager/option/detailwidget.cpp index bf41543f2..fd761927a 100644 --- a/src/plugins/aimanager/option/detailwidget.cpp +++ b/src/plugins/aimanager/option/detailwidget.cpp @@ -7,6 +7,7 @@ #include "aimanager.h" #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include DWIDGET_USE_NAMESPACE @@ -97,12 +99,14 @@ class DetailWidgetPrivate DListView *modelsView = nullptr; LLMModels *LLMModel = nullptr; AddModelDialog *addModelDialog = nullptr; + DComboBox *cbCompletedLLM = nullptr; }; DetailWidget::DetailWidget(QWidget *parent) : PageWidget(parent), d(new DetailWidgetPrivate()) { setupUi(); + addDefaultLLM(); } DetailWidget::~DetailWidget() @@ -118,6 +122,15 @@ void DetailWidget::setupUi() QVBoxLayout *vLayout = new QVBoxLayout(this); vLayout->setContentsMargins(0, 0, 0, 0); + QHBoxLayout *completedLLMLayout = new QHBoxLayout; + completedLLMLayout->setContentsMargins(10, 0, 10, 0); + d->cbCompletedLLM = new DComboBox(this); + d->cbCompletedLLM->addItem(tr("Disabled"), QVariant()); + QLabel *lbCompletedLLM = new QLabel(tr("Auto Complete LLM:"), this); + completedLLMLayout->addWidget(lbCompletedLLM); + completedLLMLayout->addWidget(d->cbCompletedLLM); + vLayout->addLayout(completedLLMLayout); + auto listframe = new DFrame(this); auto listlayout = new QVBoxLayout(listframe); listlayout->setContentsMargins(5, 5, 5, 5); @@ -165,7 +178,10 @@ void DetailWidget::setupUi() auto dialog = new AddModelDialog(this); auto code = dialog->exec(); if (code == QDialog::Accepted) { - d->LLMModel->appendLLM(dialog->getNewLLmInfo()); + auto newLLM = dialog->getNewLLmInfo(); + d->LLMModel->appendLLM(newLLM); + if (d->cbCompletedLLM->findData(newLLM.toVariant()) != -1) + d->cbCompletedLLM->addItem(newLLM.modelName, newLLM.toVariant()); } dialog->deleteLater(); }); @@ -176,24 +192,57 @@ void DetailWidget::setupUi() if (!index.isValid()) return; auto llmInfo = d->LLMModel->allLLMs().at(index.row()); + if (llmInfo.type == LLMType::ZHIPU_CODEGEEX) { // default codegeex can not remove + DDialog dialog; + dialog.setMessage(tr("You can't delete default CodeGeeX`s LLM!")); + dialog.setWindowTitle(tr("Delete Warining")); + dialog.setIcon(QIcon::fromTheme("dialog-warning")); + dialog.insertButton(0, tr("Ok")); + dialog.exec(); + return; + } d->LLMModel->removeLLM(llmInfo); + if (d->cbCompletedLLM->findData(llmInfo.toVariant()) != -1) + d->cbCompletedLLM->removeItem(d->cbCompletedLLM->findData(llmInfo.toVariant())); }); } +void DetailWidget::addDefaultLLM() +{ + auto LLMs = AiManager::instance()->getDefaultLLM(); + for (auto llm : LLMs) { + d->LLMModel->appendLLM(llm); + d->cbCompletedLLM->addItem(llm.modelName, llm.toVariant()); + } +} + bool DetailWidget::getControlValue(QMap &map) { QVariantList LLMs; for (auto llmInfo : d->LLMModel->allLLMs()) { LLMs.append(llmInfo.toVariant()); } + map.insert(kCATEGORY_CUSTOMMODELS, LLMs); + map.insert(kCATEGORY_AUTO_COMPLETE, d->cbCompletedLLM->currentData()); return true; } void DetailWidget::setControlValue(const QMap &map) { for (auto mapData : map.value(kCATEGORY_CUSTOMMODELS).toList()) { - d->LLMModel->appendLLM(LLMInfo::fromVariantMap(mapData.toMap())); + auto llmInfo = LLMInfo::fromVariantMap(mapData.toMap()); + if (llmInfo.type == LLMType::ZHIPU_CODEGEEX) // default model is already exist + continue; + d->LLMModel->appendLLM(llmInfo); + if (d->cbCompletedLLM->findData(llmInfo.toVariant()) == -1) + d->cbCompletedLLM->addItem(llmInfo.modelName, llmInfo.toVariant()); + } + if (!map.value(kCATEGORY_AUTO_COMPLETE).isValid()) { + d->cbCompletedLLM->setCurrentIndex(0); + } else { + auto selectedCompleteLLM = LLMInfo::fromVariantMap(map.value(kCATEGORY_AUTO_COMPLETE).toMap()); + d->cbCompletedLLM->setCurrentText(selectedCompleteLLM.modelName); } } diff --git a/src/plugins/aimanager/option/detailwidget.h b/src/plugins/aimanager/option/detailwidget.h index ef28e2693..cc6042f75 100644 --- a/src/plugins/aimanager/option/detailwidget.h +++ b/src/plugins/aimanager/option/detailwidget.h @@ -78,6 +78,8 @@ class DetailWidget : public PageWidget private: void setupUi(); + void addDefaultLLM(); // codegeex-chat-pro / codegeex-4 + bool getControlValue(QMap &map); void setControlValue(const QMap &map); diff --git a/src/plugins/codegeex/codegeex.cpp b/src/plugins/codegeex/codegeex.cpp index c732ca452..e4522f8f3 100644 --- a/src/plugins/codegeex/codegeex.cpp +++ b/src/plugins/codegeex/codegeex.cpp @@ -53,11 +53,6 @@ bool CodeGeex::start() connect(&dpf::Listener::instance(), &dpf::Listener::pluginsStarted, [=] { QTimer::singleShot(5000, windowService, [=] { - bool ret = CodeGeeXManager::instance()->isLoggedIn(); - if (!ret) { - QStringList actions { "codegeex_login_default", CodeGeex::tr("Login") }; - windowService->notify(0, "CodeGeex", CodeGeex::tr("Please login to use CodeGeeX."), actions); - } #ifdef SUPPORTMINIFORGE if (!CodeGeeXManager::instance()->condaHasInstalled()) { QStringList actions { "ai_rag_install", CodeGeex::tr("Install") }; @@ -71,11 +66,9 @@ bool CodeGeex::start() using namespace std::placeholders; auto aiService = dpfGetService(dpfservice::AiService); - aiService->available = std::bind(&CodeGeeXManager::isLoggedIn, CodeGeeXManager::instance()); - aiService->askQuestion = std::bind(&CodeGeeXManager::independentAsking, CodeGeeXManager::instance(), _1, QMultiMap(), _2); - aiService->askQuestionWithHistory = std::bind(&CodeGeeXManager::independentAsking, CodeGeeXManager::instance(), _1, _2, _3); aiService->generateRag = std::bind(&CodeGeeXManager::generateRag, CodeGeeXManager::instance(), _1); aiService->query = std::bind(&CodeGeeXManager::query, CodeGeeXManager::instance(), _1, _2, _3); + aiService->chatWithAi = std::bind(&CodeGeeXManager::requestAsync, CodeGeeXManager::instance(), _1); return true; } diff --git a/src/plugins/codegeex/codegeex.qrc b/src/plugins/codegeex/codegeex.qrc index 5d6d313d4..2dbf05e09 100644 --- a/src/plugins/codegeex/codegeex.qrc +++ b/src/plugins/codegeex/codegeex.qrc @@ -9,8 +9,6 @@ builtin/icons/codegeex_anwser_icon_24px.svg builtin/icons/codegeex_user_30px.svg builtin/icons/codegeex_logo_24px.svg - builtin/icons/codegeex_model_lite_16px.svg - builtin/icons/codegeex_model_pro_16px.svg builtin/texts/codegeex_indicate_16px.svg builtin/texts/codegeex_advice_16px.svg builtin/texts/codegeex_comment_16px.svg diff --git a/src/plugins/codegeex/codegeex/askapi.cpp b/src/plugins/codegeex/codegeex/askapi.cpp deleted file mode 100644 index 08373c8f8..000000000 --- a/src/plugins/codegeex/codegeex/askapi.cpp +++ /dev/null @@ -1,573 +0,0 @@ -// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later -#include "askapi.h" -#include "codegeexmanager.h" -#include "src/common/supportfile/language.h" -#include "services/project/projectservice.h" -#include "services/window/windowservice.h" -#include "services/editor/editorservice.h" -#include "common/type/constants.h" -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace CodeGeeX { -static const QString PrePrompt = "你是一个智能编程助手,可以回答用户任何的问题。问题中可能会带有相关的context,这些context来自工程相关的文件,你要结合这些上下文回答用户问题。 请注意:\n1.用户问题中以@符号开始的标记代表着context内容。/n2.按正确的语言回答,不要被上下文中的字符影响."; - -static int kCode_Success = 200; - -class AskApiPrivate : public QObject -{ -public: - explicit AskApiPrivate(AskApi *qq); - - QNetworkReply *postMessage(const QString &url, const QString &token, const QByteArray &body); - QNetworkReply *getMessage(const QString &url, const QString &token); - void processResponse(QNetworkReply *reply); - Entry processJsonObject(const QString &event, QJsonObject *obj); - - QByteArray assembleSSEChatBody(const QString &prompt, - const QString &machineId, - const QJsonArray &history, - const QString &talkId); - - QByteArray assembleNewSessionBody(const QString &prompt, - const QString &talkId); - - QByteArray assembleDelSessionBody(const QStringList &talkIds); - - QByteArray jsonToByteArray(const QJsonObject &jsonObject); - QJsonObject toJsonOBject(QNetworkReply *reply); - QJsonArray parseFile(QStringList files); - -public: - AskApi *q; - - QNetworkAccessManager *manager = nullptr; - QString model = chatModelLite; - QString locale = "zh"; - bool codebaseEnabled = false; - bool networkEnabled = false; - bool terminated = false; - QStringList referenceFiles; -}; - -AskApiPrivate::AskApiPrivate(AskApi *qq) - : q(qq), - manager(new QNetworkAccessManager(qq)) -{ - connect(q, &AskApi::stopReceive, this, [=]() { terminated = true; }); -} - -QNetworkReply *AskApiPrivate::postMessage(const QString &url, const QString &token, const QByteArray &body) -{ - if (terminated) - return nullptr; - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("code-token", token.toUtf8()); - - if (QThread::currentThread() != qApp->thread()) { - QNetworkAccessManager* threadManager(new QNetworkAccessManager); - AskApi::connect(QThread::currentThread(), &QThread::finished, threadManager, &QNetworkAccessManager::deleteLater); - return threadManager->post(request, body); - } - return manager->post(request, body); -} - -QNetworkReply *AskApiPrivate::getMessage(const QString &url, const QString &token) -{ - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - request.setRawHeader("code-token", token.toUtf8()); - - if (QThread::currentThread() != qApp->thread()) { - QNetworkAccessManager* threadManager(new QNetworkAccessManager); - AskApi::connect(QThread::currentThread(), &QThread::finished, threadManager, &QNetworkAccessManager::deleteLater); - return threadManager->get(request); - } - return manager->get(request); -} - -void AskApiPrivate::processResponse(QNetworkReply *reply) -{ - connect(reply, &QNetworkReply::readyRead, [=]() { - if (reply->error()) { - qCritical() << "Error:" << reply->errorString(); - } else { - QString replyMsg = QString::fromUtf8(reply->readAll()); - QStringList lines = replyMsg.split('\n'); - QString event; - QString id; - - for (const auto &line : lines) { - auto index = line.indexOf(':'); - auto key = line.mid(0, index); - auto value = line.mid(index + 1); - - if (key == "event") { - event = value.trimmed(); - } else if (key == "id") { - id = value.trimmed(); - } else if (key == "data") { - QJsonParseError error; - QJsonDocument jsonDocument = QJsonDocument::fromJson(value.toUtf8(), &error); - - QJsonObject jsonObject = jsonDocument.object(); - auto entry = processJsonObject(event, &jsonObject); - if (error.error != QJsonParseError::NoError) { - qCritical() << "JSON parse error: " << error.errorString(); - if (event == "finish") { - emit q->response(id, entry.text, event); - return; - } - continue; - } - - if (entry.type == "crawl") - emit q->crawledWebsite(id, entry.websites); - else - emit q->response(id, entry.text, event); - } - } - } - }); -} - -Entry AskApiPrivate::processJsonObject(const QString &event, QJsonObject *obj) -{ - Entry entry; - if (!obj || obj->isEmpty()) - return entry; - - if (event == "add") { - entry.type = "text"; - entry.text = obj->value("text").toString(); - return entry; - } - - if (event == "processing") { - auto type = obj->value("type").toString(); - entry.type = type; - if (type == "keyword") { - auto keyWords = obj->value("data").toArray(); - QString keys; - for (auto key : keyWords) - keys = keys + key.toString() + " "; - entry.text = keys.trimmed(); - } else if (type == "crawl") { - auto crawlObj = obj->value("data").toObject(); - for (auto it = crawlObj.begin(); it != crawlObj.end(); ++it) { - websiteReference website; - QString citationKey = it.key(); - QJsonObject citationObj = it.value().toObject(); - - website.citation = citationKey; - website.status = citationObj["status"].toString(); - website.url = citationObj["url"].toString(); - website.title = citationObj["title"].toString(); - - entry.websites.append(website); - } - } - return entry; - } - - if (event == "finish") { - entry.text = obj->value("text").toString(); - entry.type = event; - } - - return entry; -} - -QByteArray AskApiPrivate::assembleSSEChatBody(const QString &prompt, const QString &machineId, - const QJsonArray &history, const QString &talkId) -{ - QJsonObject jsonObject; - jsonObject.insert("ide", qApp->applicationName()); - jsonObject.insert("ide_version", version()); - jsonObject.insert("prompt", prompt); - jsonObject.insert("machineId", machineId); - jsonObject.insert("history", history); - jsonObject.insert("locale", locale); - jsonObject.insert("model", model); - - if (!referenceFiles.isEmpty()) { - auto fileDatas = parseFile(referenceFiles); - jsonObject.insert("command", "file_augment"); - QJsonObject files; - files["files"] = fileDatas; - jsonObject.insert("files", files); - } else if (networkEnabled) - jsonObject.insert("command", "online_search_v1"); - - if (!talkId.isEmpty()) - jsonObject.insert("talkId", talkId); - - return jsonToByteArray(jsonObject); -} - -QByteArray AskApiPrivate::assembleNewSessionBody(const QString &prompt, const QString &talkId) -{ - QJsonObject jsonObject; - jsonObject.insert("prompt", prompt); - jsonObject.insert("talkId", talkId); - - return jsonToByteArray(jsonObject); -} - -QByteArray AskApiPrivate::assembleDelSessionBody(const QStringList &talkIds) -{ - QString ret = "[\n"; - for (auto talkId : talkIds) { - ret += "\""; - ret += talkId; - ret += "\"\n"; - } - ret += "]"; - - return ret.toLatin1(); -} - -QByteArray AskApiPrivate::jsonToByteArray(const QJsonObject &jsonObject) -{ - QJsonDocument doc(jsonObject); - return doc.toJson(); -} - -QJsonObject AskApiPrivate::toJsonOBject(QNetworkReply *reply) -{ - QString response = QString::fromUtf8(reply->readAll()); - QJsonDocument document = QJsonDocument::fromJson(response.toUtf8()); - return document.object(); -} - -QJsonArray AskApiPrivate::parseFile(QStringList files) -{ - QJsonArray result; - auto editorSrv = dpfGetService(dpfservice::EditorService); - - for (auto file : files) { - QJsonObject obj; - obj["name"] = QFileInfo(file).fileName(); - obj["language"] = support_file::Language::id(file); - - QString fileContent = editorSrv->fileText(file); - - if (fileContent.isEmpty()) { - QFile content(file); - if (content.open(QIODevice::ReadOnly)) { - obj["content"] = QString(content.read(20000)); - } - } else { - obj["content"] = QString(fileContent.mid(0, 20000)); - } - result.append(obj); - } - - return result; -} - -AskApi::AskApi(QObject *parent) - : QObject(parent), - d(new AskApiPrivate(this)) -{ - connect(this, &AskApi::syncSendMessage, this, &AskApi::slotSendMessage); - connect(this, &AskApi::notify, this, [](int type, const QString &message) { - using namespace dpfservice; - WindowService *windowService = dpfGetService(WindowService); - windowService->notify(type, "Ai", message, QStringList {}); - }); -} - -AskApi::~AskApi() -{ - delete d; -} - -void AskApi::sendLoginRequest(const QString &sessionId, - const QString &machineId, - const QString &userId, - const QString &env) -{ - QString url = QString("https://codegeex.cn/auth?sessionId=%1&%2=%3&device=%4").arg(sessionId).arg(machineId).arg(userId).arg(env); - QDesktopServices::openUrl(QUrl(url)); -} - -void AskApi::logout(const QString &codeToken) -{ - QString url = "https://codegeex.cn/prod/code/oauth/logout"; - - QNetworkReply *reply = d->getMessage(url, codeToken); - connect(reply, &QNetworkReply::finished, [=]() { - if (reply->error()) { - qCritical() << "Error:" << reply->errorString(); - return; - } - QJsonObject jsonObject = d->toJsonOBject(reply); - int code = jsonObject["code"].toInt(); - if (code == kCode_Success) { - emit loginState(kLoginOut); - } else { - qWarning() << "logout failed"; - } - }); -} - -void AskApi::sendQueryRequest(const QString &codeToken) -{ - QString url = "https://codegeex.cn/prod/code/oauth/getUserInfo"; - - QNetworkReply *reply = d->getMessage(url, codeToken); - connect(reply, &QNetworkReply::finished, this, [=]() { - if (reply->error()) { - qCritical() << "Error:" << reply->errorString(); - return; - } - QJsonObject jsonObject = d->toJsonOBject(reply); - int code = jsonObject["code"].toInt(); - if (code == kCode_Success) { - emit loginState(kLoginSuccess); - } else { - emit loginState(kLoginFailed); - } - }); -} - -QJsonArray convertHistoryToJSONArray(const QMultiMap &history) -{ - QJsonArray jsonArray; - - for (auto it = history.constBegin(); it != history.constEnd(); ++it) { - const QString &key = it.key(); - const QString &value = it.value(); - - QJsonObject jsonObject; - jsonObject["query"] = key; - jsonObject["answer"] = value; - - jsonArray.append(jsonObject); - } - - return jsonArray; -} - -void AskApi::slotSendMessage(const QString url, const QString &token, const QByteArray &body) -{ - QNetworkReply *reply = d->postMessage(url, token, body); - connect(this, &AskApi::stopReceive, reply, [reply]() { - reply->abort(); - }); - d->processResponse(reply); -} - -void AskApi::postSSEChat(const QString &url, - const QString &token, - const QString &prompt, - const QString &machineId, - const QMultiMap &history, - const QString &talkId) -{ - d->terminated = false; - QJsonArray jsonArray = convertHistoryToJSONArray(history); - -#ifdef SUPPORTMINIFORGE - auto impl = CodeGeeXManager::instance(); - impl->checkCondaInstalled(); - if (d->codebaseEnabled && !impl->condaHasInstalled()) { // if not x86 or arm. @codebase can not be use - QStringList actions { "ai_rag_install", tr("Install") }; - dpfservice::WindowService *windowService = dpfGetService(dpfservice::WindowService); - windowService->notify(0, "AI", CodeGeeXManager::tr("The file indexing feature is not available, which may cause functions such as @codebase to not work properly." - "Please install the required environment.\n the installation process may take several minutes."), - actions); - } -#endif - - QByteArray body = d->assembleSSEChatBody(prompt, machineId, jsonArray, talkId); - if (!body.isEmpty()) - emit syncSendMessage(url, token, body); -} - -QString AskApi::syncQuickAsk(const QString &url, - const QString &token, - const QString &prompt, - const QString &talkId) -{ - d->terminated = false; - - QJsonObject jsonObject; - jsonObject.insert("ide", qApp->applicationName()); - jsonObject.insert("ide_version", version()); - jsonObject.insert("prompt", prompt); - jsonObject.insert("history", {}); - jsonObject.insert("locale", d->locale); - jsonObject.insert("model", chatModelLite); - jsonObject.insert("talkId", talkId); - jsonObject.insert("stream", false); - - QByteArray body = d->jsonToByteArray(jsonObject); - QNetworkReply *reply = d->postMessage(url, token, body); - QEventLoop loop; - connect(reply, &QNetworkReply::finished, reply, [&]() { - loop.exit(); - }); - connect(this, &AskApi::stopReceive, reply, [reply, &loop]() { - reply->abort(); - loop.exit(); - }); - loop.exec(); - - QJsonParseError error; - QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll(), &error); - QJsonObject obj = jsonDocument.object(); - if (error.error != QJsonParseError::NoError) { - qCritical() << "JSON parse error: " << error.errorString(); - return ""; - } - - return obj["text"].toString(); -} - -void AskApi::postNewSession(const QString &url, - const QString &token, - const QString &prompt, - const QString &talkId) -{ - d->terminated = false; - QByteArray body = d->assembleNewSessionBody(prompt, talkId); - QNetworkReply *reply = d->postMessage(url, token, body); - connect(reply, &QNetworkReply::finished, [=]() { - if (reply->error()) { - qCritical() << "Error:" << reply->errorString(); - return; - } - QJsonObject jsonObject = d->toJsonOBject(reply); - int code = jsonObject["code"].toInt(); - - emit sessionCreated(talkId, code == kCode_Success); - }); -} - -void AskApi::getSessionList(const QString &url, const QString &token, int pageNumber, int pageSize) -{ - QString urlWithParameter = QString(url + "?pageNum=%1&pageSize=%2").arg(pageNumber).arg(pageSize); - QNetworkReply *reply = d->getMessage(urlWithParameter, token); - connect(reply, &QNetworkReply::finished, [=]() { - if (reply->error()) { - qCritical() << "Error:" << reply->errorString(); - return; - } - QJsonObject jsonObject = d->toJsonOBject(reply); - int code = jsonObject["code"].toInt(); - if (code == kCode_Success) { - QJsonArray listArray = jsonObject.value("data").toObject().value("list").toArray(); - QVector records; - for (int i = 0; i < listArray.size(); ++i) { - SessionRecord record; - QJsonObject item = listArray[i].toObject(); - record.talkId = item.value("talkId").toString(); - record.createdTime = item.value("createTime").toString(); - record.prompt = item.value("prompt").toString(); - - records.append(record); - } - emit getSessionListResult(records); - } - }); -} - -void AskApi::getMessageList(const QString &url, const QString &token, int pageNumber, int pageSize, const QString &talkId) -{ - QString urlWithParameter = QString(url + "?pageNum=%1&pageSize=%2&talkId=%3").arg(pageNumber).arg(pageSize).arg(talkId); - QNetworkReply *reply = d->getMessage(urlWithParameter, token); - connect(reply, &QNetworkReply::finished, [=]() { - if (reply->error()) { - qCritical() << "Error:" << reply->errorString(); - return; - } - QJsonObject jsonObject = d->toJsonOBject(reply); - - int code = jsonObject["code"].toInt(); - if (code == kCode_Success) { - QJsonArray listArray = jsonObject.value("data").toObject().value("list").toArray(); - QVector records; - for (int i = 0; i < listArray.size(); ++i) { - MessageRecord record; - QJsonObject item = listArray[i].toObject(); - record.input = item.value("prompt").toString(); - record.output = item.value("outputText").toString(); - - records.append(record); - } - emit getMessageListResult(records); - } - }); -} - -void AskApi::deleteSessions(const QString &url, const QString &token, const QStringList &talkIds) -{ - d->terminated = false; - QByteArray body = d->assembleDelSessionBody(talkIds); - QNetworkReply *reply = d->postMessage(url, token, body); - connect(reply, &QNetworkReply::finished, [=]() { - if (reply->error()) { - qCritical() << "Error:" << reply->errorString(); - return; - } - QJsonObject jsonObject = d->toJsonOBject(reply); - int code = jsonObject["code"].toInt(); - emit sessionDeleted(talkIds, code == kCode_Success); - }); -} - -void AskApi::setModel(const QString &model) -{ - d->model = model; -} - -void AskApi::setLocale(const QString &locale) -{ - d->locale = locale; -} - -void AskApi::setNetworkEnabled(bool enabled) -{ - d->networkEnabled = enabled; -} - -bool AskApi::networkEnabled() const -{ - return d->networkEnabled; -} - -void AskApi::setReferenceFiles(const QStringList &fileList) -{ - d->referenceFiles = fileList; -} - -QStringList AskApi::referenceFiles() const -{ - return d->referenceFiles; -} - -void AskApi::setCodebaseEnabled(bool enabled) -{ - d->codebaseEnabled = enabled; -} - -bool AskApi::codebaseEnabled() const -{ - return d->codebaseEnabled; -} - -} // end namespace diff --git a/src/plugins/codegeex/codegeex/askapi.h b/src/plugins/codegeex/codegeex/askapi.h deleted file mode 100644 index 2c04d059b..000000000 --- a/src/plugins/codegeex/codegeex/askapi.h +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later -#ifndef ASKAPI_H -#define ASKAPI_H - -#include - -class QNetworkAccessManager; -class QNetworkReply; - -namespace CodeGeeX { -struct websiteReference -{ - QString citation; - QString url; - QString title; - QString status; -}; - -struct Entry -{ - QString type; - QString text; - QList websites; -}; - -class AskApiPrivate; -class AskApi : public QObject -{ - Q_OBJECT -public: - struct SessionRecord - { - QString prompt; - QString talkId; - QString createdTime; - }; - - struct MessageRecord - { - QString input; - QString output; - }; - - enum LoginState { - kLoginFailed, - kLoginSuccess, - kLoginOut - }; - - explicit AskApi(QObject *parent = nullptr); - ~AskApi(); - - void sendLoginRequest(const QString &sessionId, - const QString &machineId, - const QString &userId, - const QString &env = "deepin-unioncode"); - - void logout(const QString &codeToken); - - void sendQueryRequest(const QString &codeToken); - - void postSSEChat(const QString &url, - const QString &token, - const QString &prompt, - const QString &machineId, - const QMultiMap &history, - const QString &talkId); - - QString syncQuickAsk(const QString &url, - const QString &token, - const QString &prompt, - const QString &talkId); - - void postNewSession(const QString &url, - const QString &token, - const QString &prompt, - const QString &talkId); - - void getSessionList(const QString &url, - const QString &token, - int pageNumber = 1, - int pageSize = 10); - - void getMessageList(const QString &url, - const QString &token, - int pageNumber = 1, - int pageSize = 10, - const QString &talkId = ""); - - void deleteSessions(const QString &url, - const QString &token, - const QStringList &talkIds); - - void setModel(const QString &model); - void setLocale(const QString &locale); - void setNetworkEnabled(bool enabled); - bool networkEnabled() const; - void setReferenceFiles(const QStringList &fileList); - QStringList referenceFiles() const; - void setCodebaseEnabled(bool enabled); - bool codebaseEnabled() const; - -signals: - void loginState(LoginState loginState); - void response(const QString &msgID, const QString &response, const QString &event); - void crawledWebsite(const QString &msgID, const QList &websites); - void getSessionListResult(const QVector &records); - void getMessageListResult(const QVector &records); - void sessionDeleted(const QStringList &talkId, bool isSuccessful); - void sessionCreated(const QString &talkId, bool isSuccessful); - void stopReceive(); - void syncSendMessage(const QString url, const QString &token, const QByteArray &body); - void noChunksFounded(); - void notify(int type, const QString &message); - -public slots: - void slotSendMessage(const QString url, const QString &token, const QByteArray &body); - -private: - AskApiPrivate *const d; -}; -} // end namespace - -#endif // ASKAPI_H diff --git a/src/plugins/codegeex/codegeex/codegeexcompletionprovider.cpp b/src/plugins/codegeex/codegeex/codegeexcompletionprovider.cpp deleted file mode 100644 index 5aa7702c3..000000000 --- a/src/plugins/codegeex/codegeex/codegeexcompletionprovider.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "codegeexcompletionprovider.h" -#include "copilot.h" - -using namespace CodeGeeX; - -CodeGeeXCompletionProvider::CodeGeeXCompletionProvider(QObject *parent) - : AbstractInlineCompletionProvider(parent) -{ - timer.setSingleShot(true); - timer.setInterval(500); -} - -QString CodeGeeXCompletionProvider::providerName() const -{ - return "CodeGeeX"; -} - -void CodeGeeXCompletionProvider::provideInlineCompletionItems(const Position &pos, const InlineCompletionContext &c) -{ - for (const auto &item : qAsConst(completionItems)) { - if (c.prefix.endsWith(item.completion)) - return; - } - - positon = pos; - context = c; - connect(&timer, &QTimer::timeout, Copilot::instance(), &Copilot::generateCode, Qt::UniqueConnection); - timer.start(); -} - -QList CodeGeeXCompletionProvider::inlineCompletionItems() const -{ - return completionItems; -} - -void CodeGeeXCompletionProvider::setInlineCompletionEnabled(bool enabled) -{ - if (!enabled && timer.isActive()) - timer.stop(); - - completionEnabled = enabled; -} - -void CodeGeeXCompletionProvider::setInlineCompletions(const QStringList &completions) -{ - completionItems.clear(); - for (const auto &completion : completions) { - InlineCompletionItem item { completion, positon }; - completionItems << item; - } -} - -bool CodeGeeXCompletionProvider::inlineCompletionEnabled() const -{ - return completionEnabled; -} - -void CodeGeeXCompletionProvider::accepted() -{ - completionItems.clear(); -} - -void CodeGeeXCompletionProvider::rejected() -{ - completionItems.clear(); -} - -AbstractInlineCompletionProvider::InlineCompletionContext CodeGeeXCompletionProvider::inlineCompletionContext() const -{ - return context; -} diff --git a/src/plugins/codegeex/codegeex/copilotapi.cpp b/src/plugins/codegeex/codegeex/copilotapi.cpp deleted file mode 100644 index d2b5d424f..000000000 --- a/src/plugins/codegeex/codegeex/copilotapi.cpp +++ /dev/null @@ -1,406 +0,0 @@ -// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later -#include "copilotapi.h" -#include "src/common/supportfile/language.h" -#include "src/services/editor/editorservice.h" -#include "src/services/project/projectservice.h" -#include "services/window/windowservice.h" - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -using namespace dpfservice; - -void sendNotify(uint type, const QString &name, const QString &msg) -{ - auto windowSrv = dpfGetService(WindowService); - windowSrv->notify(type, name, msg, {}); -} - -namespace CodeGeeX { -CopilotApi::CopilotApi(QObject *parent) - : QObject(parent), manager(new QNetworkAccessManager(this)) -{ - connect(this, &CopilotApi::asyncGenerateMessages, this, &CopilotApi::slotPostGenerateMessage); -} - -void CopilotApi::slotPostGenerateMessage(const QString &url, const QByteArray &body) -{ - QNetworkReply *reply = postMessage(url, CodeGeeXManager::instance()->getSessionId(), body); - reply->setProperty("responseType", CopilotApi::inline_completions); - completionReply = reply; - processResponse(reply); -} - -void CopilotApi::postGenerate(const QString &url, const QString &prefix, const QString &suffix, GenerateType type) -{ - if (completionReply) - completionReply->close(); - QtConcurrent::run([prefix, suffix, type, url, this]() { - QByteArray body = assembleGenerateBody(prefix, suffix, type); - emit asyncGenerateMessages(url, body); - }); -} - -void CopilotApi::postComment(const QString &url, - const QString &code, - const QString &locale) -{ - QByteArray body = assembleCommandBody(code, locale, "comment"); - QNetworkReply *reply = postMessage(url, CodeGeeXManager::instance()->getSessionId(), body); - reply->setProperty("responseType", CopilotApi::multilingual_code_comment); - - processResponse(reply); -} - -void CopilotApi::postInlineChat(const QString &url, - const QString &prompt, - const InlineChatInfo &info, - const QString &locale) -{ - QByteArray body = assembleInlineChatBody(prompt, info, locale); - QNetworkReply *reply = postMessage(url, CodeGeeXManager::instance()->getSessionId(), body); - reply->setProperty("responseType", CopilotApi::inline_chat); - - processResponse(reply); -} - -void CopilotApi::postCommit(const QString &url, - const CommitMessage &message, - const QString &locale) -{ - QByteArray body = assembleCommitBody(message, locale); - QNetworkReply *reply = postMessage(url, CodeGeeXManager::instance()->getSessionId(), body); - reply->setProperty("responseType", CopilotApi::receiving_by_stream); - - processResponse(reply); -} - -void CopilotApi::postReview(const QString &url, - const QString &code, - const QString &locale) -{ - QByteArray body = assembleReviewBody(code, locale); - QNetworkReply *reply = postMessage(url, CodeGeeXManager::instance()->getSessionId(), body); - reply->setProperty("responseType", CopilotApi::receiving_by_stream); - - processResponse(reply); -} - -void CopilotApi::postCommand(const QString &url, - const QString &code, - const QString &locale, - const QString &command) -{ - QByteArray body = assembleCommandBody(code, locale, command); - QNetworkReply *reply = postMessage(url, CodeGeeXManager::instance()->getSessionId(), body); - reply->setProperty("responseType", CopilotApi::receiving_by_stream); - - processResponse(reply); -} - -QNetworkReply *CopilotApi::postMessage(const QString &url, - const QString &token, - const QByteArray &body) -{ - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("code-token", token.toUtf8()); - - return manager->post(request, body); -} - -/* - data = { - "context": [{ - "kind": - "active_document":{} - }, - { - "kind": - "document":{} - }, - ], - "model":, - "lang": - } -*/ -QByteArray CopilotApi::assembleGenerateBody(const QString &prefix, const QString &suffix, GenerateType type) -{ - auto file = getCurrentFileInfo(); - - QJsonObject activeDocument; - activeDocument.insert("path", file.first); - activeDocument.insert("prefix", prefix); - activeDocument.insert("suffix", suffix); - activeDocument.insert("lang", file.second); - - QJsonObject activeContextItem; - activeContextItem.insert("kind", "active_document"); - activeContextItem.insert("active_document", activeDocument); - - ProjectService *prjSrv = dpfGetService(ProjectService); - QJsonArray context; - context.append(activeContextItem); - QJsonObject queryResults = CodeGeeXManager::instance()->query(prjSrv->getActiveProjectInfo().workspaceFolder(), prefix, 5); - QJsonArray chunks = queryResults["Chunks"].toArray(); - - for (auto chunk : chunks) { - QJsonObject document; - document.insert("path", chunk.toObject()["fileName"].toString()); - document.insert("text", chunk.toObject()["content"].toString()); - document.insert("lang", file.second); - - QJsonObject contextItem; - contextItem.insert("kind", "document"); - contextItem.insert("document", document); - context.append(contextItem); - } - - QJsonObject json; - json.insert("ide", qApp->applicationName()); - json.insert("ide_version", version()); - json.insert("context", context); - json.insert("model", completionModel); - json.insert("lang", file.second); - if (type == GenerateType::Line) - json.insert("max_new_tokens", 64); - else - json.insert("max_new_tokens", 128); - - QJsonDocument doc(json); - return doc.toJson(); -} - -QByteArray CopilotApi::assembleInlineChatBody(const QString &prompt, const InlineChatInfo &info, const QString &locale) -{ - auto file = getCurrentFileInfo(); - - QJsonObject json; - json.insert("ide", qApp->applicationName()); - json.insert("ide_version", version()); - json.insert("lang", file.second); - json.insert("code", info.selectedCode); - json.insert("command", "inline_chat"); - json.insert("locale", locale); - json.insert("talkId", CodeGeeXManager::instance()->getTalkId()); - json.insert("model", chatModel); - QString promptWithType = prompt; - if (info.commandType == InlineChatInfo::Chat) - promptWithType.append(".use Chat type to answer me"); - else if (info.commandType == InlineChatInfo::Programing) - promptWithType.append(".use Programing type to answer me"); - json.insert("prompt", promptWithType); - - QJsonObject inline_chat_obj; - inline_chat_obj.insert("is_ast", info.is_ast); - inline_chat_obj.insert("file_name", info.fileName); - inline_chat_obj.insert("package_code", info.package_code); - inline_chat_obj.insert("class_function", info.class_function); - inline_chat_obj.insert("context_code", info.contextCode); - - json.insert("inline_chat", inline_chat_obj); - QJsonDocument doc(json); - return doc.toJson(); -} - -QByteArray CopilotApi::assembleCommitBody(const CommitMessage &message, const QString &locale) -{ - QJsonObject json; - json.insert("ide", qApp->applicationName()); - json.insert("ide_version", version()); - json.insert("command", "commit_message_v1"); - json.insert("talkId", CodeGeeXManager::instance()->getTalkId()); - json.insert("locale", locale); - json.insert("model", chatModel); - - QJsonObject commitObj; - commitObj.insert("git_diff", message.git_diff); - commitObj.insert("commit_history", message.commit_history); - commitObj.insert("commit_type", message.commit_type); - - json.insert("commit_message", commitObj); - - QJsonDocument doc(json); - return doc.toJson(); -} - -QByteArray CopilotApi::assembleReviewBody(const QString &code, const QString &locale) -{ - QJsonObject json; - json.insert("ide", qApp->applicationName()); - json.insert("ide_version", version()); - json.insert("prompt", "你是一位智能编程助手,你叫CodeGeeX。你会为用户回答关于编程、代码、计算机方面的任何问题,并提供格式规范、可以执行、准确安全的代码,并在必要时提供详细的解释。\n任务:你现在开始扮演一个高级专家工程师,专注于代码审查和软件安全。请根据用户的代码,给出不少于3条全局性的建议。"); - json.insert("code", code); - json.insert("talkId", CodeGeeXManager::instance()->getTalkId()); - json.insert("locale", locale); - json.insert("model", chatModel); - - QJsonDocument doc(json); - return doc.toJson(); -} - -QByteArray CopilotApi::assembleCommandBody(const QString &code, const QString &locale, const QString &command) -{ - QJsonObject json; - json.insert("ide", qApp->applicationName()); - json.insert("ide_version", version()); - json.insert("command", command); - json.insert("code", code); - json.insert("talkId", CodeGeeXManager::instance()->getTalkId()); - json.insert("locale", locale); - json.insert("model", chatModel); - - QJsonDocument doc(json); - return doc.toJson(); -} - -void CopilotApi::processResponse(QNetworkReply *reply) -{ - connect(this, &CopilotApi::requestStop, this, [=]() { reply->close(); }); - if (reply->property("responseType") == CopilotApi::receiving_by_stream) { - connect(reply, &QNetworkReply::readyRead, this, [=]() { - slotReadReplyStream(reply); - }); - } else { - connect(reply, &QNetworkReply::finished, this, [=]() { - slotReadReply(reply); - }); - } -} - -void CopilotApi::slotReadReply(QNetworkReply *reply) -{ - if (reply->error() != QNetworkReply::NoError) { - qCritical() << "Error:" << reply->errorString() << reply->error(); - if (reply->error() != QNetworkReply::OperationCanceledError) { - auto type = reply->property("responseType").value(); - sendNotify(2, "AI" ,reply->errorString()); - emit response(type, "", ""); - } - } else { - QString replyMsg = QString::fromUtf8(reply->readAll()); - QJsonParseError error; - QJsonDocument jsonDocument = QJsonDocument::fromJson(replyMsg.toUtf8(), &error); - auto type = reply->property("responseType").value(); - if (error.error != QJsonParseError::NoError) { - qCritical() << "JSON parse error: " << error.errorString(); - emit response(type, "", ""); - return; - } - - QJsonObject jsonObject = jsonDocument.object(); - QString code {}; - if (type == CopilotApi::inline_completions) { - auto content = jsonObject.value("inline_completions").toArray().at(0).toObject(); - code = content.value("text").toString(); - if (content.value("finish_reason").toString() == "length") { - // Due to the length limit of the code, the last line will be discarded when the code is truncated. - auto codeLines = code.split('\n'); - if (codeLines.size() > 1) - codeLines.removeLast(); - code = codeLines.join('\n'); - } - - completionReply = nullptr; - - // all '\n' - if (code.split('\n', QString::SkipEmptyParts).isEmpty()) - return; - emit response(CopilotApi::inline_completions, code, ""); - } else if (type == CopilotApi::multilingual_code_comment) { - code = jsonObject.value("text").toString(); - emit response(CopilotApi::multilingual_code_comment, code, ""); - } else if (type == CopilotApi::inline_chat) { - code = jsonObject.value("text").toString(); - emit response(CopilotApi::inline_chat, code, ""); - } - } -} - -void CopilotApi::slotReadReplyStream(QNetworkReply *reply) -{ - if (reply->error()) { - qCritical() << "Error:" << reply->errorString(); - } else { - QString replyMsg = QString::fromUtf8(reply->readAll()); - QStringList lines = replyMsg.split('\n'); - QString data; - QString event; - QString id; - - for (const auto &line : lines) { - auto index = line.indexOf(':'); - auto key = line.mid(0, index); - auto value = line.mid(index + 1); - - if (key == "event") { - event = value.trimmed(); - } else if (key == "id") { - id = value.trimmed(); - } else if (key == "data") { - QJsonParseError error; - QJsonDocument jsonDocument = QJsonDocument::fromJson(value.toUtf8(), &error); - - if (error.error != QJsonParseError::NoError) { - qCritical() << "JSON parse error: " << error.errorString(); - return; - } - - QJsonObject jsonObject = jsonDocument.object(); - data = jsonObject.value("text").toString(); - - emit responseByStream(id, data, event); - } - } - } -} - -void CopilotApi::setModel(languageModel model) -{ - if (model == CodeGeeX::Lite) { - chatModel = chatModelLite; - completionModel = completionModelLite; - } else if (model == CodeGeeX::Pro) { - chatModel = chatModelPro; - completionModel = completionModelPro; - } -} - -languageModel CopilotApi::model() const -{ - if (chatModel == chatModelLite) - return Lite; - return Pro; -} - -QPair CopilotApi::getCurrentFileInfo() -{ - auto &ctx = dpfInstance.serviceContext(); - EditorService *editorService = ctx.service(EditorService::name()); - auto filePath = editorService->currentFile(); - QString fileName; - if (QFileInfo(filePath).exists()) - fileName = QFileInfo(filePath).fileName(); - else - fileName = "main.cpp"; - auto fileType = support_file::Language::id(filePath); - auto fileLang = support_file::Language::idAlias(fileType); - - // The above LANGUAGE class supports fewer file languages, and unknown file languages are temporarily represented by suffix. - if (fileLang.isEmpty()) - fileLang = QFileInfo(filePath).suffix(); - return qMakePair(fileName, fileLang); -} - -} // end namespace diff --git a/src/plugins/codegeex/codegeex/copilotapi.h b/src/plugins/codegeex/codegeex/copilotapi.h deleted file mode 100644 index d565330b5..000000000 --- a/src/plugins/codegeex/codegeex/copilotapi.h +++ /dev/null @@ -1,246 +0,0 @@ -// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later -#ifndef COPILOTAPI_H -#define COPILOTAPI_H - -#include "codegeexmanager.h" - -#include -#include - -class QNetworkAccessManager; -class QNetworkReply; - -namespace CodeGeeX { -static QList SupportLanguage { - "python", - "go", - "java", - "javascript", - "c++", - "c#", - "php", - "typescript", - "c", - "css", - "cuda", - "dart", - "lua", - "objective-c", - "objective-c++", - "perl", - "prolog", - "swift", - "lisp", - "scala", - "tex", - "rust", - "markdown", - "html", - "vue", - "shell", - "sql", - "kotlin", - "visual basic", - "ruby", - "pascal", - "r", - "fortran", - "lean", - "matlab", - "delphi", - "scheme", - "basic", - "assembly", - "groovy", - "abap", - "gdscript", - "haskell", - "julia", - "elixir", - "excel", - "clojure", - "actionscript", - "solidity", - "powershell", - "erlang", - "cobol", - "alloy", - "awk", - "thrift", - "sparql", - "augeas", - "f#", - "cmake", - "stan", - "isabelle", - "dockerfile", - "rmarkdown", - "literate agda", - "glsl", - "antlr", - "verilog", - "racket", - "standard ml", - "elm", - "yaml", - "smalltalk", - "ocaml", - "idris", - "protocal buffer", - "bluespec", - "applescript", - "makefile", - "maple", - "tcsh", - "systemverilog", - "literate coffeescript", - "vhdl", - "restructuredtext", - "sas", - "literate haskell", - "java server pages", - "coffeescript", - "emacs lisp", - "mathematica", - "xslt", - "ada", - "zig", - "common lisp", - "staga", - "agda", -}; - -static const QStringList ALL_AST_LANGS = { - "javascript", - "typescript", - "javascript jsx", - "javascriptreact", - "typescript jsx", - "typescriptreact", - "go", - "ruby", - "csharp", - "c#", - "c", - "cpp", - "c++", - "java", - "rust", - "python", -}; - -struct InlineChatInfo { - enum CommandType{ - Programing, - Chat - }; - QString fileName { "" }; - QString package_code { "" }; // eg: "10 import path \n11 import os \n15 import pygame" - QString class_function { "" }; // if is_ast is false, class_function do not be set. or set it to "all class and function definition in current file" - QString selectedCode { "" }; // need to contains [cursor] - QString contextCode { "" }; // linenumber before code - CommandType commandType { Programing }; - bool is_ast { false }; // true when above langs contains this file type and file content > 100 lines -}; - -struct CommitMessage { - QString git_diff { "" }; - QString commit_history { "" }; - QString commit_type { "conventional" }; // conventional / auto / default -}; - -class CopilotApi : public QObject -{ - Q_OBJECT - -public: - enum GenerateType { - Line, - Block - }; - - CopilotApi(QObject *parent = nullptr); - void setModel(languageModel model); - languageModel model() const; - - void postGenerate(const QString &url, const QString &prefix, const QString &suffix, GenerateType type); - - void postComment(const QString &url, - const QString &code, - const QString &locale); - - void postReview(const QString &url, - const QString &code, - const QString &locale); - - void postInlineChat(const QString &url, - const QString &prompt, - const InlineChatInfo &info, - const QString &locale); // codegeex: this api is non-streaming. url need to be xxx/?stream=false - - void postCommit(const QString &url, - const CommitMessage &message, - const QString &locale); - - void postCommand(const QString &url, - const QString &code, - const QString &locale, - const QString &command); - - enum ResponseType { - inline_completions, - inline_chat, - multilingual_code_comment, - receiving_by_stream - }; - -signals: - void response(ResponseType responseType, const QString &response, const QString &dstLang); - void responseByStream(const QString &msgID, const QString &response, const QString &event); - void asyncGenerateMessages(const QString &url, const QByteArray &body); - - void requestStop(); - void messageSended(); - -public slots: - void slotReadReply(QNetworkReply *reply); - void slotReadReplyStream(QNetworkReply *reply); - void slotPostGenerateMessage(const QString &url, const QByteArray &body); - -private: - QNetworkReply *postMessage(const QString &url, const QString &token, const QByteArray &body); - - QByteArray assembleGenerateBody(const QString &prefix, - const QString &suffix, - GenerateType type); - - QByteArray assembleInlineChatBody(const QString &prompt, - const InlineChatInfo &info, - const QString &locale); - - QByteArray assembleCommitBody(const CommitMessage &message, - const QString &locale); - - QByteArray assembleReviewBody(const QString &code, - const QString &locale); - - QByteArray assembleCommandBody(const QString &code, - const QString &locale, - const QString &command); - - void processResponse(QNetworkReply *reply); - QPair getCurrentFileInfo(); - - QNetworkAccessManager *manager = nullptr; - QString chatModel = chatModelLite; - QString completionModel = completionModelLite; - - QNetworkReply *completionReply = nullptr; -}; - -} // end namespace -Q_DECLARE_METATYPE(CodeGeeX::CopilotApi::ResponseType) - -#endif // COPILOT_H diff --git a/src/plugins/codegeex/codegeexmanager.cpp b/src/plugins/codegeex/codegeexmanager.cpp index 5c3f5c50f..6141c43a3 100644 --- a/src/plugins/codegeex/codegeexmanager.cpp +++ b/src/plugins/codegeex/codegeexmanager.cpp @@ -10,6 +10,7 @@ #include "services/terminal/terminalservice.h" #include "services/project/projectservice.h" #include "services/option/optionmanager.h" +#include "services/editor/editorservice.h" #include @@ -26,39 +27,44 @@ #include #include -static const char *kUrlSSEChat = "https://codegeex.cn/prod/code/chatCodeSseV3/chat"; -//static const char *kUrlSSEChat = "https://codegeex.cn/prod/code/chatGlmSse/chat"; -static const char *kUrlNewSession = "https://codegeex.cn/prod/code/chatGlmTalk/insert"; -static const char *kUrlDeleteSession = "https://codegeex.cn/prod/code/chatGlmTalk/delete"; -static const char *kUrlQuerySession = "https://codegeex.cn/prod/code/chatGlmTalk/selectList"; -static const char *kUrlQueryMessage = "https://codegeex.cn/prod/code/chatGmlMsg/selectList"; +// might useful later. +//static const char *kUrlQueryMessage = "https://codegeex.cn/prod/code/chatGmlMsg/selectList"; + +static const QString PrePrompt = "Use the above code to answer the following question. You should not reference any files outside of what is shown, unless they are commonly known files, like a .gitignore or package.json. Reference the filenames whenever possible. If there isn't enough information to answer the question, suggest where the user might look to learn more"; using namespace CodeGeeX; -using dpfservice::WindowService; +using namespace dpfservice; -CodeGeeXManager *CodeGeeXManager::instance() +QJsonArray parseFile(QStringList files) { - static CodeGeeXManager ins; - return &ins; -} + QJsonArray result; + auto editorSrv = dpfGetService(EditorService); -void CodeGeeXManager::login() -{ - if (sessionId.isEmpty() || userId.isEmpty()) { - sessionId = uuid(); - userId = uuid(); - saveConfig(sessionId, userId); - } + for (auto file : files) { + QJsonObject obj; + obj["name"] = QFileInfo(file).fileName(); + obj["language"] = support_file::Language::id(file); + + QString fileContent = editorSrv->fileText(file); - QString machineId = QSysInfo::machineUniqueId(); - askApi.sendLoginRequest(sessionId, machineId, userId); + if (fileContent.isEmpty()) { + QFile content(file); + if (content.open(QIODevice::ReadOnly)) { + obj["content"] = QString(content.read(20000)); + } + } else { + obj["content"] = QString(fileContent.mid(0, 20000)); + } + result.append(obj); + } - queryLoginState(); + return result; } -bool CodeGeeXManager::isLoggedIn() const +CodeGeeXManager *CodeGeeXManager::instance() { - return isLogin; + static CodeGeeXManager ins; + return &ins; } void CodeGeeXManager::checkCondaInstalled() @@ -82,143 +88,56 @@ bool CodeGeeXManager::condaHasInstalled() return condaInstalled; } -void CodeGeeXManager::saveConfig(const QString &sessionId, const QString &userId) -{ - QVariantMap map { { "sessionId", sessionId }, - { "userId", userId } }; - - OptionManager::getInstance()->setValue("CodeGeeX", "Id", map); -} - -Q_DECL_DEPRECATED_X("-------------存在兼容代码需要删除") -void CodeGeeXManager::loadConfig() -{ - QFile file(configFilePath()); - if (!file.exists()) { - const auto map = OptionManager::getInstance()->getValue("CodeGeeX", "Id").toMap(); - if (map.isEmpty()) - return; - - sessionId = map.value("sessionId").toString(); - userId = map.value("userId").toString(); - } else { - // ------------------Deprecated start-------------------- - file.open(QIODevice::ReadOnly); - QString data = QString::fromUtf8(file.readAll()); - file.close(); - file.remove(); - - QJsonDocument document = QJsonDocument::fromJson(data.toUtf8()); - QJsonObject config = document.object(); - if (!config.empty()) { - sessionId = config["sessionId"].toString(); - userId = config["userId"].toString(); - saveConfig(sessionId, userId); - } - // ------------------Deprecated end-------------------- - } -} - -void CodeGeeXManager::setLocale(CodeGeeX::locale locale) +AbstractLLM *CodeGeeXManager::getCurrentLLM() const { - if (locale == CodeGeeX::Zh) { - askApi.setLocale("zh"); - Copilot::instance()->setLocale("zh"); - } else if (locale == CodeGeeX::En) { - askApi.setLocale("en"); - Copilot::instance()->setLocale("en"); - } + return chatLLM; } -void CodeGeeXManager::setCurrentModel(languageModel model) +void CodeGeeXManager::setLocale(CodeGeeX::Locale locale) { - Copilot::instance()->setCurrentModel(model); - if (model == Lite) - askApi.setModel(chatModelLite); - else if (model == Pro) - askApi.setModel(chatModelPro); + this->locale = locale; + chatLLM->setLocale(locale == CodeGeeX::Zh ? AbstractLLM::Zh : AbstractLLM::En); + Copilot::instance()->setLocale(locale); } void CodeGeeXManager::connectToNetWork(bool connecting) { - askApi.setNetworkEnabled(connecting); + networkEnabled = connecting; } bool CodeGeeXManager::isConnectToNetWork() const { - return askApi.networkEnabled(); -} - -QStringList CodeGeeXManager::getReferenceFiles() const -{ - return askApi.referenceFiles(); + return networkEnabled; } void CodeGeeXManager::setReferenceCodebase(bool on) { - askApi.setCodebaseEnabled(on); + codebaseEnabled = on; } bool CodeGeeXManager::isReferenceCodebase() const { - return askApi.codebaseEnabled(); + return codebaseEnabled; } void CodeGeeXManager::setReferenceFiles(const QStringList &files) { - askApi.setReferenceFiles(files); -} - -void CodeGeeXManager::independentAsking(const QString &prompt, const QMultiMap &history, QIODevice *pipe) -{ - if (!isLoggedIn()) { - emit notify(1, tr("CodeGeeX is not avaliable, please logging in")); - pipe->close(); - return; - } - AskApi *api = new AskApi; - api->postSSEChat(kUrlSSEChat, sessionId, prompt, QSysInfo::machineUniqueId(), history, currentTalkID); - QTimer::singleShot(10000, api, [=]() { - if (pipe && pipe->isOpen()) { - qWarning() << "timed out, close pipe"; - pipe->close(); - api->deleteLater(); - emit notify(1, tr("Request timed out, please check the network or if the model is available.")); - } - }); - - connect(api, &AskApi::response, api, [=](const QString &msgID, const QString &data, const QString &event) { - QString msgData = modifiedData(data); - if (event == "finish") { - api->deleteLater(); - pipe->close(); - return; - } else if (event == "add") { - pipe->write(msgData.toUtf8()); - } - }); + referenceFiles = files; } -void CodeGeeXManager::createNewSession() +QStringList CodeGeeXManager::getReferenceFiles() { - QString currentMSecsStr = QString::number(QDateTime::currentMSecsSinceEpoch()); - QString sessionTitle("Session_" + currentMSecsStr); - QString taskId(uuid()); - askApi.postNewSession(kUrlNewSession, sessionId, sessionTitle, taskId); + return referenceFiles; } void CodeGeeXManager::deleteCurrentSession() { - if (currentTalkID.isEmpty()) - return; - - askApi.deleteSessions(kUrlDeleteSession, sessionId, { currentTalkID }); - createNewSession(); + auto c = chatLLM->getCurrentConversation(); + c->clear(); } void CodeGeeXManager::deleteSession(const QString &talkId) { - askApi.deleteSessions(kUrlDeleteSession, sessionId, { talkId }); } void CodeGeeXManager::setMessage(const QString &prompt) @@ -237,19 +156,19 @@ QString CodeGeeXManager::getChunks(const QString &queryText) QJsonArray chunks = result["Chunks"].toArray(); if (!chunks.isEmpty()) { if (result["Completed"].toBool() == false) - emit askApi.notify(0, CodeGeeXManager::tr("The indexing of project %1 has not been completed, which may cause the results to be inaccurate.").arg(currentProjectPath)); + emit notify(0, tr("The indexing of project %1 has not been completed, which may cause the results to be inaccurate.").arg(currentProjectPath)); QString context; context += "\n\n"; for (auto chunk : chunks) { context += chunk.toObject()["fileName"].toString(); - context += '\n'; + context += '\n```'; context += chunk.toObject()["content"].toString(); - context += "\n\n"; + context += "```\n\n"; } context += "\n"; return context; } else if (CodeGeeXManager::instance()->condaHasInstalled()) { - emit askApi.noChunksFounded(); + emit noChunksFounded(); return ""; } } @@ -259,62 +178,128 @@ QString CodeGeeXManager::getChunks(const QString &queryText) QString CodeGeeXManager::promptPreProcessing(const QString &originText) { - QString processedText = originText; + QString processedText = PrePrompt; QString message = originText; - if (askApi.codebaseEnabled()) { - QString prompt = QString("Translate this passage into English :\"%1\", with the requirements: Do not provide responses other than translation.").arg(message.remove("@CodeBase")); - auto englishPrompt = askApi.syncQuickAsk(kUrlSSEChat, sessionId, prompt, currentTalkID); - QString chunksContext = getChunks(englishPrompt); - if (!chunksContext.isEmpty()) - message.append(chunksContext); - processedText = originText + chunksContext; +#ifdef SUPPORTMINIFORGE + if (isReferenceCodebase()) { + if (!condaHasInstalled()) { // if not x86 or arm. @codebase can not be use + QStringList actions { "ai_rag_install", tr("Install") }; + emit notify(0, CodeGeeXManager::tr("The file indexing feature is not available, which may cause functions such as @codebase to not work properly." + "Please install the required environment.\n the installation process may take several minutes."), + actions); + } else { + QString prompt = QString("Translate this passage into English :\"%1\", with the requirements: Do not provide responses other than translation.").arg(message.remove("@CodeBase")); + auto englishPrompt = requestSync(prompt); + QString chunksContext = getChunks(englishPrompt); + if (chunksContext.isEmpty()) + return ""; + if (message.contains("@CodeBase")) + message.remove("@CodeBase"); + processedText.append("\n\n" + chunksContext + "\n"); + } + } +#endif + if (!getReferenceFiles().isEmpty()) { + QJsonArray files = parseFile(getReferenceFiles()); + for (auto file : files) { + auto fileName = file.toObject()["name"].toString(); + auto language = file.toObject()["language"].toString(); + auto content = file.toObject()["content"].toString(); + + processedText.append("\n" + fileName + "\n```" + language + "\n" + content + "```\n"); + } } - return processedText; + if (processedText != PrePrompt) { + processedText.append("\n\n" + message); + if (locale == CodeGeeX::Zh) + processedText.append("\nPlease answer me by Chinese"); + return processedText; + } else { + return originText; + } } void CodeGeeXManager::sendMessage(const QString &prompt) { + if (!chatLLM) { + emit notify(2, tr("No selected LLM or current LLM is not avaliable")); + return; + } + QString askId = "User" + QString::number(QDateTime::currentMSecsSinceEpoch()); MessageData msgData(askId, MessageData::Ask); msgData.updateData(prompt); Q_EMIT requestMessageUpdate(msgData); - if (currentChat.first.isEmpty()) - currentChat.first = prompt; - QMultiMap history {}; - for (auto chat : chatHistory) { - history.insert(chat.first, chat.second); - } - QString machineId = QSysInfo::machineUniqueId(); - QString talkId = currentTalkID; + requestAsync(prompt); +} + +//For chatting: Using user-defined models +void CodeGeeXManager::requestAsync(const QString &prompt) +{ + if (!chatLLM || chatLLM->modelState() == AbstractLLM::Busy) + return; - QtConcurrent::run([=, this](){ + answerFlag++; + startReceiving(); + QtConcurrent::run([=](){ auto processedText = promptPreProcessing(prompt); - askApi.postSSEChat(kUrlSSEChat, sessionId, processedText, machineId, history, talkId); + if (processedText.isEmpty()) + return; + auto c = chatLLM->getCurrentConversation(); + c->addUserData(processedText); + QJsonObject obj = chatLLM->create(*c); + if (isConnectToNetWork()) + obj.insert("command", "online_search_v1"); // only worked on CodeGeeX llm + emit sendSyncRequest(obj); }); +} - startReceiving(); +//For quick processing of special requests +QString CodeGeeXManager::requestSync(const QString &prompt) +{ + QEventLoop loop; + QString response; + connect(liteLLM, &AbstractLLM::dataReceived, &loop, [=, &loop, &response](const QString &data, AbstractLLM::ResponseState state) { + response = data; + loop.exit(); + }); + // use liteLLM to handle request. quickly get the response + liteLLM->request(prompt); + loop.exec(); + return response; } -void CodeGeeXManager::onSessionCreated(const QString &talkId, bool isSuccessful) +void CodeGeeXManager::slotSendSyncRequest(const QJsonObject &obj) { - if (isSuccessful) { - currentTalkID = talkId; - Q_EMIT createdNewSession(); - } else { - qWarning() << "Create session failed!"; - } + if (chatLLM) + chatLLM->request(obj); + else + emit notify(2, tr("No selected LLM or current LLM is not avaliable")); } -void CodeGeeXManager::onResponse(const QString &msgID, const QString &data, const QString &event) +void CodeGeeXManager::onResponse(const QString &msgID, const QString &data, AbstractLLM::ResponseState state) { if (msgID.isEmpty()) return; + if (state == AbstractLLM::ResponseState::Canceled || state == AbstractLLM::Failed) { + return; + } + auto msgData = modifiedData(data); - if (event == "finish") { + if (state == AbstractLLM::ResponseState::Receiving) { + responseData += msgData; + if (!curSessionMsg.contains(msgID)) + curSessionMsg.insert(msgID, MessageData(msgID, MessageData::Anwser)); + + if (!data.isEmpty()) { + curSessionMsg[msgID].updateData(responseData); + Q_EMIT requestMessageUpdate(curSessionMsg[msgID]); + } + } else { if (responseData.isEmpty() && !data.isEmpty()) { responseData = msgData; if (!curSessionMsg.contains(msgID)) @@ -324,76 +309,36 @@ void CodeGeeXManager::onResponse(const QString &msgID, const QString &data, cons } responseData.clear(); - if (!currentChat.first.isEmpty() && currentChat.second.isEmpty()) { - currentChat.second = msgData; - chatHistory.append(currentChat); - chatRecord empty {}; - currentChat.swap(empty); - } isRunning = false; emit chatFinished(); return; - } else if (event == "add") { - responseData += msgData; - if (!curSessionMsg.contains(msgID)) - curSessionMsg.insert(msgID, MessageData(msgID, MessageData::Anwser)); - - if (!data.isEmpty()) { - curSessionMsg[msgID].updateData(responseData); - Q_EMIT requestMessageUpdate(curSessionMsg[msgID]); - } } } -void CodeGeeXManager::recevieLoginState(AskApi::LoginState loginState) +void CodeGeeXManager::onLLMChanged(const LLMInfo &llmInfo) { - if (loginState == AskApi::LoginState::kLoginFailed) { - //qWarning() << "CodeGeeX login failed!"; - // switch to login ui. - } else if (loginState == AskApi::LoginState::kLoginSuccess) { - isLogin = true; - Q_EMIT loginSuccessed(); - // switch to ask page. - if (queryTimer) { - queryTimer->stop(); - queryTimer->deleteLater(); - queryTimer = nullptr; + if (chatLLM) { + if (chatLLM->modelState() == AbstractLLM::Busy) { + emit terminated(); + stopReceiving(); + chatLLM->cancel(); } - } else if (loginState == AskApi::LoginState::kLoginOut) { - isLogin = false; - Q_EMIT logoutSuccessed(); - } -} - -void CodeGeeXManager::recevieSessionRecords(const QVector &records) -{ - sessionRecordList.clear(); - - for (auto record : records) { - RecordData data; - data.talkId = record.talkId; - data.promot = record.prompt; - data.date = record.createdTime; - sessionRecordList.append(data); + disconnect(chatLLM, &AbstractLLM::dataReceived, this, nullptr); + disconnect(chatLLM, &AbstractLLM::customDataReceived, this, nullptr); + disconnect(this, &CodeGeeXManager::requestStop, chatLLM, &AbstractLLM::cancel); + }; + + auto aiSrv = dpfGetService(AiService); + auto selectedLLM = aiSrv->getLLM(llmInfo); + if (!selectedLLM) { + QString error = tr("llm named: %1 is not avaliable.").arg(llmInfo.modelName); + emit notify(1, error); + return; } - Q_EMIT sessionRecordsUpdated(); -} - -void CodeGeeXManager::showHistoryMessage(const QVector &records) -{ - for (auto index = records.size() - 1; index >= 0; index--) { - auto messageId = QString::number(QDateTime::currentMSecsSinceEpoch()); - auto record = records[index]; - - MessageData askData(messageId + "Ask", MessageData::Ask); - askData.updateData(record.input); - Q_EMIT requestMessageUpdate(askData); - - MessageData ansData(messageId + "Anwser", MessageData::Anwser); - ansData.updateData(record.output); - Q_EMIT requestMessageUpdate(ansData); - } + chatLLM = selectedLLM; + initLLM(chatLLM); + emit llmChanged(llmInfo); } void CodeGeeXManager::recevieDeleteResult(const QStringList &talkIds, bool success) @@ -417,69 +362,81 @@ void CodeGeeXManager::recevieDeleteResult(const QStringList &talkIds, bool succe CodeGeeXManager::CodeGeeXManager(QObject *parent) : QObject(parent) { + auto aiSrv = dpfGetService(AiService); + auto liteLLMInfo = aiSrv->getCodeGeeXLLMLite(); + liteLLM = aiSrv->getLLM(liteLLMInfo); + liteLLM->setStream(false); + initConnections(); - loadConfig(); - queryLoginState(); } void CodeGeeXManager::initConnections() { - connect(&askApi, &AskApi::response, this, &CodeGeeXManager::onResponse); - connect(&askApi, &AskApi::crawledWebsite, this, &CodeGeeXManager::crawledWebsite); - connect(&askApi, &AskApi::loginState, this, &CodeGeeXManager::recevieLoginState); - connect(&askApi, &AskApi::sessionCreated, this, &CodeGeeXManager::onSessionCreated); - connect(&askApi, &AskApi::getSessionListResult, this, &CodeGeeXManager::recevieSessionRecords); - connect(&askApi, &AskApi::sessionDeleted, this, &CodeGeeXManager::recevieDeleteResult); - connect(&askApi, &AskApi::getMessageListResult, this, &CodeGeeXManager::showHistoryMessage); - connect(&askApi, &AskApi::noChunksFounded, this, &CodeGeeXManager::showIndexingWidget); + connect(this, &CodeGeeXManager::noChunksFounded, this, &CodeGeeXManager::showIndexingWidget); - connect(Copilot::instance(), &Copilot::response, this, &CodeGeeXManager::onResponse); connect(Copilot::instance(), &Copilot::messageSended, this, &CodeGeeXManager::startReceiving); - connect(this, &CodeGeeXManager::requestStop, &askApi, &AskApi::stopReceive); connect(this, &CodeGeeXManager::requestStop, Copilot::instance(), &Copilot::requestStop); - connect(this, &CodeGeeXManager::notify, this, [](int type, const QString &message) { + connect(this, &CodeGeeXManager::notify, this, [](int type, const QString &message, QStringList actions) { WindowService *windowService = dpfGetService(WindowService); - windowService->notify(type, "Ai", message, QStringList {}); + windowService->notify(type, "Ai", message, actions); + }); + connect(this, &CodeGeeXManager::sendSyncRequest, this, &CodeGeeXManager::slotSendSyncRequest); + connect(this, &CodeGeeXManager::llmChanged, Copilot::instance(), [=](const LLMInfo &info){ + auto aiSrv = dpfGetService(AiService); + auto copilotLLM = aiSrv->getLLM(info); // Use a new LLM to avoid affecting chatLLM + if (copilotLLM) + Copilot::instance()->setCopilotLLM(copilotLLM); }); } -void CodeGeeXManager::queryLoginState() -{ - if (!queryTimer) { - queryTimer = new QTimer(this); - connect(queryTimer, &QTimer::timeout, this, [=] { - if (!sessionId.isEmpty()) - askApi.sendQueryRequest(sessionId); - }); - } - - queryTimer->start(1000); -} - -void CodeGeeXManager::logout() +void CodeGeeXManager::initLLM(AbstractLLM *llm) { - if (!isLogin) { - qWarning() << "cant`t logout without login"; + if (!llm) return; - } - askApi.logout(sessionId); -} - -void CodeGeeXManager::cleanHistoryMessage() -{ - chatHistory.clear(); - curSessionMsg.clear(); + chatLLM->setLocale(locale == CodeGeeX::Zh ? AbstractLLM::Zh : AbstractLLM::En); + connect(chatLLM, &AbstractLLM::dataReceived, this, [=](const QString &data, AbstractLLM::ResponseState state) { + if (state == AbstractLLM::Canceled) + return; + if (state == AbstractLLM::Failed) { + QString errStr; + bool valid = chatLLM->checkValid(&errStr); + if (!valid) + emit notify(2, tr("LLM is not valid. %1").arg(errStr)); + else + emit notify(2, tr("Error: %1, try again later").arg(data)); + return; + } + onResponse(QString::number(answerFlag), data, state); + }); + connect(chatLLM, &AbstractLLM::customDataReceived, this, [=](const QString &key, const QJsonObject &obj) { + QList websites; + if (key == "crawl") { + for (auto it = obj.begin(); it != obj.end(); ++it) { + websiteReference website; + QString citationKey = it.key(); + QJsonObject citationObj = it.value().toObject(); + + website.citation = citationKey; + website.status = citationObj["status"].toString(); + website.url = citationObj["url"].toString(); + website.title = citationObj["title"].toString(); + + websites.append(website); + } + emit crawledWebsite(QString::number(answerFlag), websites); + } + }); + connect(this, &CodeGeeXManager::requestStop, chatLLM, &AbstractLLM::cancel); } +// todo: storage of session records void CodeGeeXManager::fetchSessionRecords() { - askApi.getSessionList(kUrlQuerySession, sessionId, 1, 50); } void CodeGeeXManager::fetchMessageList(const QString &talkId) { - askApi.getMessageList(kUrlQueryMessage, sessionId, 1, 50, talkId); } void CodeGeeXManager::startReceiving() @@ -492,8 +449,6 @@ void CodeGeeXManager::stopReceiving() { isRunning = false; responseData.clear(); - chatRecord empty {}; - currentChat.swap(empty); emit requestStop(); } @@ -507,27 +462,6 @@ QList CodeGeeXManager::sessionRecords() const return sessionRecordList; } -QString CodeGeeXManager::configFilePath() const -{ - return CustomPaths::user(CustomPaths::Configures) + "/codegeexcfg.json"; -} - -QString CodeGeeXManager::uuid() -{ - QUuid uuid = QUuid::createUuid(); - return uuid.toString().replace("{", "").replace("}", "").replace("-", ""); -} - -QString CodeGeeXManager::getSessionId() const -{ - return sessionId; -} - -QString CodeGeeXManager::getTalkId() const -{ - return currentTalkID; -} - QString CodeGeeXManager::modifiedData(const QString &data) { auto retData = data; diff --git a/src/plugins/codegeex/codegeexmanager.h b/src/plugins/codegeex/codegeexmanager.h index ef11ab432..1bbc83b03 100644 --- a/src/plugins/codegeex/codegeexmanager.h +++ b/src/plugins/codegeex/codegeexmanager.h @@ -5,8 +5,9 @@ #ifndef CODEGEEXMANAGER_H #define CODEGEEXMANAGER_H -#include "codegeex/askapi.h" #include "data/messagedata.h" +#include "base/ai/abstractllm.h" +#include "services/ai/aiservice.h" #include #include @@ -15,6 +16,14 @@ #include #include +struct websiteReference +{ + QString citation; + QString url; + QString title; + QString status; +}; + struct RecordData { QString talkId; @@ -23,56 +32,37 @@ struct RecordData }; namespace CodeGeeX { -static const char *chatModelLite = "codegeex-4"; -static const char *chatModelPro = "codegeex-chat-pro"; - -static const char *completionModelLite = "codegeex-lite"; -static const char *completionModelPro = "codegeex-pro"; - #if defined(__x86_64__)// || defined(__aarch64__) #define SUPPORTMINIFORGE #endif -enum languageModel { - Lite, - Pro -}; - -enum locale { +enum Locale { Zh, En }; } -Q_DECLARE_METATYPE(CodeGeeX::languageModel) -Q_DECLARE_METATYPE(CodeGeeX::locale) +Q_DECLARE_METATYPE(CodeGeeX::Locale) -typedef QPair chatRecord; class CodeGeeXManager : public QObject { Q_OBJECT public: static CodeGeeXManager *instance(); - Q_INVOKABLE void login(); - bool isLoggedIn() const; void checkCondaInstalled(); bool condaHasInstalled(); - void saveConfig(const QString &sessionId, const QString &userId); - void loadConfig(); + AbstractLLM *getCurrentLLM() const; - void setLocale(CodeGeeX::locale locale); - void setCurrentModel(CodeGeeX::languageModel model); + void setLocale(CodeGeeX::Locale locale); - void createNewSession(); void deleteCurrentSession(); void deleteSession(const QString &talkId); void setMessage(const QString &prompt); void sendMessage(const QString &prompt); - void queryLoginState(); - - void cleanHistoryMessage(); + void requestAsync(const QString &prompt); + QString requestSync(const QString &prompt); // Sync use another llm to get response. not chat void fetchSessionRecords(); void fetchMessageList(const QString &talkId); @@ -80,8 +70,6 @@ class CodeGeeXManager : public QObject void stopReceiving(); bool checkRunningState(bool state); - QString getSessionId() const; - QString getTalkId() const; QList sessionRecords() const; void connectToNetWork(bool connecting); @@ -90,8 +78,8 @@ class CodeGeeXManager : public QObject void setReferenceCodebase(bool on); bool isReferenceCodebase() const; void setReferenceFiles(const QStringList &files); + QStringList getReferenceFiles(); - void independentAsking(const QString &prompt, const QMultiMap &history, QIODevice *pipe); // Rag QString condaRootPath() const; void showIndexingWidget(); @@ -110,60 +98,58 @@ class CodeGeeXManager : public QObject QString getChunks(const QString &queryText); Q_SIGNALS: - void loginSuccessed(); - void logoutSuccessed(); - void createdNewSession(); + void sendSyncRequest(const QJsonObject &obj); void requestMessageUpdate(const MessageData &msg); void chatStarted(); - void crawledWebsite(const QString &msgID, const QList &websites); - void searching(const QString &searchText); + void crawledWebsite(const QString &msgID, const QList &websites); void terminated(); void chatFinished(); void sessionRecordsUpdated(); void setTextToSend(const QString &prompt); void requestStop(); - void notify(int type, const QString &message); + void notify(int type, const QString &message, QStringList actions = {}); void showCustomWidget(QWidget *widget); void generateDone(const QString &path, bool failed); + void noChunksFounded(); void quit(); + void llmChanged(const LLMInfo &info); public Q_SLOTS: - void onSessionCreated(const QString &talkId, bool isSuccessful); - void onResponse(const QString &msgID, const QString &data, const QString &event); - void recevieLoginState(CodeGeeX::AskApi::LoginState loginState); - void recevieSessionRecords(const QVector &records); + void slotSendSyncRequest(const QJsonObject &obj); + void onResponse(const QString &msgID, const QString &data, AbstractLLM::ResponseState state); void recevieDeleteResult(const QStringList &talkIds, bool success); - void showHistoryMessage(const QVector &records); - void logout(); + void onLLMChanged(const LLMInfo &llmInfo); private: explicit CodeGeeXManager(QObject *parent = nullptr); void initConnections(); + void initLLM(AbstractLLM *llm); QString modifiedData(const QString &data); - QString configFilePath() const; - QString uuid(); - - CodeGeeX::AskApi askApi; QString sessionId; QString userId; QString currentTalkID; QString responseData; + bool codebaseEnabled = false; + bool networkEnabled = false; + QStringList referenceFiles; + QMap curSessionMsg; - QList chatHistory {}; - chatRecord currentChat {}; QList sessionRecordList {}; - QTimer *queryTimer { nullptr }; - bool isLogin { false }; bool isRunning { false }; bool condaInstalled { false }; QTimer installCondaTimer; QMutex mutex; QStringList indexingProject {}; QJsonObject currentChunks; + + int answerFlag = 0; // use to identify every single answer + AbstractLLM *chatLLM { nullptr }; + AbstractLLM *liteLLM { nullptr }; //codegeex Lite. + CodeGeeX::Locale locale { CodeGeeX::Zh }; }; #endif // CODEGEEXMANAGER_H diff --git a/src/plugins/codegeex/copilot.cpp b/src/plugins/codegeex/copilot.cpp index 67ee11f92..641ac6982 100644 --- a/src/plugins/codegeex/copilot.cpp +++ b/src/plugins/codegeex/copilot.cpp @@ -2,8 +2,8 @@ // // SPDX-License-Identifier: GPL-3.0-or-later #include "copilot.h" +#include "base/ai/abstractllm.h" #include "widgets/inlinechatwidget.h" -#include "codegeex/codegeexcompletionprovider.h" #include "services/editor/editorservice.h" #include "services/option/optionmanager.h" #include "services/window/windowservice.h" @@ -13,14 +13,9 @@ #include #include #include - -static const char *kUrlSSEChat = "https://codegeex.cn/prod/code/chatCodeSseV3/chat"; -static const char *kUrlGenerateMultiLine = "https://api.codegeex.cn:8443/v3/completions/inline?stream=false"; +#include static const char *lineChatTip = "LineChatTip"; -static const char *commandFixBug = "fixbug"; -static const char *commandExplain = "explain"; -static const char *commandTests = "tests"; using namespace CodeGeeX; using namespace dpfservice; @@ -32,52 +27,11 @@ Copilot::Copilot(QObject *parent) if (!editorService) { qFatal("Editor service is null!"); } - generateTimer = new QTimer(this); - generateTimer->setSingleShot(true); - completionProvider = new CodeGeeXCompletionProvider(this); - editorService->registerInlineCompletionProvider(completionProvider); QAction *lineChatAct = new QAction(tr("Inline Chat"), this); lineChatCmd = ActionManager::instance()->registerAction(lineChatAct, "CodeGeeX.InlineChat"); lineChatCmd->setDefaultKeySequence(Qt::CTRL + Qt::Key_T); connect(lineChatAct, &QAction::triggered, this, &Copilot::startInlineChat); - - connect(&copilotApi, &CopilotApi::response, [this](CopilotApi::ResponseType responseType, const QString &response, const QString &dstLang) { - switch (responseType) { - case CopilotApi::multilingual_code_comment: - if (!response.isEmpty()) - replaceSelectedText(response); - break; - case CopilotApi::inline_completions: - if (!responseValid(response)) - return; - { - QString completion = ""; - - if (generateType == CopilotApi::Line) { - generateCache = response.split('\n'); - completion = extractSingleLine(); - } else if (generateType == CopilotApi::Block) { - generateCache.clear(); - completion = response; - } - - if (completion.endsWith('\n')) - completion.chop(1); - - generatedCode = completion; - completionProvider->setInlineCompletions({ completion }); - emit completionProvider->finished(); - } - break; - default:; - } - }); - - connect(&copilotApi, &CopilotApi::responseByStream, this, &Copilot::response); - connect(&copilotApi, &CopilotApi::messageSended, this, &Copilot::messageSended); - connect(generateTimer, &QTimer::timeout, this, &Copilot::generateCode); - connect(this, &Copilot::requestStop, &copilotApi, &CopilotApi::requestStop); } QString Copilot::selectedText() const @@ -88,17 +42,6 @@ QString Copilot::selectedText() const return editorService->getSelectedText(); } -bool Copilot::responseValid(const QString &response) -{ - bool valid = !(response.isEmpty() - || response.startsWith("\n\n\n") - || response.startsWith("\n \n ")); - if (!valid) { - qWarning() << "Reponse not valid: " << response; - } - return valid; -} - Copilot *Copilot::instance() { static Copilot ins; @@ -146,48 +89,18 @@ void Copilot::insterText(const QString &text) editorService->insertText(text); } -void Copilot::setGenerateCodeEnabled(bool enabled) -{ - if (!enabled && generateTimer->isActive()) - generateTimer->stop(); - completionProvider->setInlineCompletionEnabled(enabled); -} - -bool Copilot::getGenerateCodeEnabled() const -{ - return completionProvider->inlineCompletionEnabled(); -} - -void Copilot::setLocale(const QString &locale) +void Copilot::setLocale(CodeGeeX::Locale locale) { this->locale = locale; } -QString Copilot::getLocale() const -{ - return locale; -} - -void Copilot::setCommitsLocale(const QString &locale) +void Copilot::setCommitsLocale(CodeGeeX::Locale locale) { this->commitsLocale = locale; } -void Copilot::setCurrentModel(CodeGeeX::languageModel model) -{ - copilotApi.setModel(model); -} - -languageModel Copilot::getCurrentModel() const -{ - return copilotApi.model(); -} - void Copilot::handleSelectionChanged(const QString &fileName, int lineFrom, int indexFrom, int lineTo, int indexTo) { - if (!CodeGeeXManager::instance()->isLoggedIn()) - return; - editorService->clearAllEOLAnnotation(lineChatTip); if (lineFrom == -1) return; @@ -205,74 +118,64 @@ void Copilot::handleInlineWidgetClosed() inlineChatWidget->reset(); } -void Copilot::addComment() -{ - QString url = QString(kUrlSSEChat) + "?stream=false"; //receive all msg at once - copilotApi.postComment(url, - selectedText(), - locale); -} - -void Copilot::generateCode() +void Copilot::setCopilotLLM(AbstractLLM *llm) { - if (!completionProvider->inlineCompletionEnabled()) - return; - - const auto &context = completionProvider->inlineCompletionContext(); - if (!context.prefix.endsWith(generatedCode) || generateCache.isEmpty()) { - generateType = checkPrefixType(context.prefix); - copilotApi.postGenerate(kUrlGenerateMultiLine, - context.prefix, - context.suffix, - generateType); - } else { - generatedCode = extractSingleLine(); - completionProvider->setInlineCompletions({ generatedCode }); - emit completionProvider->finished(); - } + copilotLLM = llm; } -void Copilot::login() +void Copilot::addComment() { + QString prompt = "You're an intelligent programming assistant." + " You'll answer any questions you may have about programming, code, or computers, and provide formatted, executable, accurate, and secure code." + " Task: Please provide a comment for the input code, including multi-line comments and single-line comments, please be careful not to change the original code, only add comments." + " Directly return the code without any Markdown formatting, such as ```, ```cpp, etc. " + "\n\ncode: ```%1```"; + copilotLLM->setStream(false); + copilotLLM->request(prompt.arg(selectedText()), [=](const QString &data, AbstractLLM::ResponseState state){ + if (state == AbstractLLM::ResponseState::Success) { + if (!data.isEmpty()) + replaceSelectedText(data); + } + }); } void Copilot::fixBug() { - QString url = QString(kUrlSSEChat) + "?stream=true"; - if (CodeGeeXManager::instance()->checkRunningState(false)) { - copilotApi.postCommand(url, assembleCodeByCurrentFile(selectedText()), locale, commandFixBug); - emit messageSended(); - } + auto currentFileText = editorService->fileText(editorService->currentFile()); + currentFileText.replace(selectedText(), "" + selectedText() + ""); + QString prompt = "code: ```%1```\n\n" + "Rewrite the code between and in this entire code block. The rewrite requirements are: fix any bugs in this code, or do a simple rewrite if there are no errors, without leaving placeholders. Answer only the code between these markers."; + CodeGeeXManager::instance()->requestAsync(prompt.arg(currentFileText)); switchToCodegeexPage(); } void Copilot::explain() { - QString url = QString(kUrlSSEChat) + "?stream=true"; - if (CodeGeeXManager::instance()->checkRunningState(false)) { - copilotApi.postCommand(url, assembleCodeByCurrentFile(selectedText()), locale, commandExplain); - emit messageSended(); - } + QString prompt = "code: ```%1```\n\n" + "You are an intelligent programming assistant. You will answer any questions users have about programming, code, and computers, providing well-formatted, executable, accurate, and secure code, and providing detailed explanations when necessary. Task: Please explain the meaning of the input code, including the implementation principle, purpose, and precautions, etc. "; + if (locale == Zh) + prompt.append("\nPlease answer by Chineses"); + CodeGeeXManager::instance()->requestAsync(prompt.arg(selectedText())); switchToCodegeexPage(); } void Copilot::review() { - QString url = QString(kUrlSSEChat) + "?stream=true"; - if (CodeGeeXManager::instance()->checkRunningState(false)) { - copilotApi.postReview(url, selectedText(), locale); - emit messageSended(); - } + QString prompt = "code: ```%1```\n\n" + "You are an intelligent programming assistant. You will answer any questions users have about programming, code, and computers, providing well-formatted, executable, accurate, and secure code, and providing detailed explanations when necessary. Now, you will start to act as an advanced expert engineer focusing on code review and software security. Please provide at least three global suggestions based on the user’s code"; + if (locale == Zh) + prompt.append("\nPlease answer by Chineses"); + CodeGeeXManager::instance()->requestAsync(prompt.arg(selectedText())); switchToCodegeexPage(); } void Copilot::tests() { - QString url = QString(kUrlSSEChat) + "?stream=true"; - if (CodeGeeXManager::instance()->checkRunningState(false)) { - copilotApi.postCommand(url, assembleCodeByCurrentFile(selectedText()), locale, commandTests); - emit messageSended(); - } + QString prompt = "code: ```%1```\n\n" + "You need to automatically determine the programming language of the provided code, and write a set of unit test code for it using a popular current unit testing framework. Please ensure that the tests cover the main functionalities and edge cases, and include necessary comments."; + if (locale == Zh) + prompt.append("\nPlease answer by Chineses"); + CodeGeeXManager::instance()->requestAsync(prompt.arg(selectedText())); switchToCodegeexPage(); } @@ -289,19 +192,15 @@ void Copilot::commits() connect(&process, QOverload::of(&QProcess::finished), this, [this, &process](int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus) - if (exitCode != 0) return; - auto diff = QString::fromUtf8(process.readAll()); - QString url = QString(kUrlSSEChat) + "?stream=true"; - - if (CodeGeeXManager::instance()->checkRunningState(false)) { - CommitMessage message; - message.git_diff = diff; - copilotApi.postCommit(url, message, commitsLocale); - emit messageSended(); - } + auto diff = QString::fromUtf8(process.read(20000)); + QString prompt = "diff: ```%1```\n\n" + "You always analyze the git diff provided, detect changes, and generate succinct yet comprehensive commit messages. for the user step-by-step:\n1. You first parse the git diff to understand the changes made: additions, deletions, modifications, or renaming.\n2. Identify the components or modules that the changes are relating to.\n3. Understand the nature of changes: bug fixes, functionality enhancements, code optimization, documentation, etc.\n4. You highlight the primary updates without neglecting any minor alterations.\n5. Choose a commit type according to the primary updates.\n6. Organize these changes into an accurate, concise and informative commit message less than 20 words.\nYou should only reply a one-line commit message less than 20 words!!!"; + if (commitsLocale == Zh) + prompt.append("\nPlease answer by Chineses"); + CodeGeeXManager::instance()->requestAsync(prompt.arg(diff)); switchToCodegeexPage(); }); @@ -344,9 +243,6 @@ void Copilot::showLineChatTip(const QString &fileName, int line) void Copilot::startInlineChat() { - if (!CodeGeeXManager::instance()->isLoggedIn()) - return; - editorService->closeLineWidget(); editorService->clearAllEOLAnnotation(lineChatTip); if (!inlineChatWidget) { @@ -354,51 +250,6 @@ void Copilot::startInlineChat() connect(inlineChatWidget, &InlineChatWidget::destroyed, this, [this] { inlineChatWidget = nullptr; }); } + inlineChatWidget->setLLM(copilotLLM); inlineChatWidget->start(); } - -CodeGeeX::CopilotApi::GenerateType Copilot::checkPrefixType(const QString &prefixCode) -{ - //todo - Q_UNUSED(prefixCode) - if (0) - return CopilotApi::Line; - else - return CopilotApi::Block; -} - -QString Copilot::extractSingleLine() -{ - if (generateCache.isEmpty()) - return ""; - - bool extractedCode = false; - QString completion = ""; - for (auto line : generateCache) { - if (extractedCode) - break; - if (line != "") - extractedCode = true; - - completion += line == "" ? "\n" : line; - generateCache.removeFirst(); - } - completion += "\n"; - - //check if left cache all '\n' - bool leftAllEmpty = true; - for (auto line : generateCache) { - if (line == "") - continue; - leftAllEmpty = false; - break; - } - if (leftAllEmpty) { - generateCache.clear(); - completion += "\n"; - } - - if (!extractedCode) - completion = ""; - return completion; -} diff --git a/src/plugins/codegeex/copilot.h b/src/plugins/codegeex/copilot.h index 41cb980f6..82e7a9def 100644 --- a/src/plugins/codegeex/copilot.h +++ b/src/plugins/codegeex/copilot.h @@ -4,7 +4,8 @@ #ifndef COPILOT_H #define COPILOT_H -#include "codegeex/copilotapi.h" +#include "services/ai/aiservice.h" +#include "codegeexmanager.h" #include #include @@ -29,26 +30,20 @@ class Copilot : public QObject void replaceSelectedText(const QString &text); void insterText(const QString &text); - void setGenerateCodeEnabled(bool enabled); - bool getGenerateCodeEnabled() const; - void setLocale(const QString &locale); - QString getLocale() const; - void setCommitsLocale(const QString &locale); - void setCurrentModel(CodeGeeX::languageModel model); - CodeGeeX::languageModel getCurrentModel() const; + + void setLocale(CodeGeeX::Locale locale); + void setCommitsLocale(CodeGeeX::Locale locale); void handleSelectionChanged(const QString &fileName, int lineFrom, int indexFrom, int lineTo, int indexTo); void handleInlineWidgetClosed(); + void setCopilotLLM(AbstractLLM *llm); signals: - void response(const QString &msgID, const QString &response, const QString &event); void messageSended(); void requestStop(); public slots: void addComment(); - void generateCode(); - void login(); void fixBug(); void explain(); void review(); @@ -58,26 +53,18 @@ public slots: private: explicit Copilot(QObject *parent = nullptr); QString selectedText() const; - QString locale { "zh" }; - QString commitsLocale { "zh" }; + CodeGeeX::Locale locale { CodeGeeX::Zh }; + CodeGeeX::Locale commitsLocale { CodeGeeX::En }; void switchToCodegeexPage(); - bool responseValid(const QString &response); QString assembleCodeByCurrentFile(const QString &code); void showLineChatTip(const QString &fileName, int line); void startInlineChat(); InlineChatWidget *inlineChatWidget = nullptr; Command *lineChatCmd = nullptr; - CodeGeeX::CopilotApi copilotApi; dpfservice::EditorService *editorService = nullptr; - QTimer *generateTimer = nullptr; - QStringList generateCache {}; - QString generatedCode {}; - QString extractSingleLine(); - CodeGeeX::CodeGeeXCompletionProvider *completionProvider = nullptr; - CodeGeeX::CopilotApi::GenerateType generateType; - CodeGeeX::CopilotApi::GenerateType checkPrefixType(const QString &prefixCode); + AbstractLLM *copilotLLM = nullptr; }; #endif // COPILOT_H diff --git a/src/plugins/codegeex/eventreceiver.cpp b/src/plugins/codegeex/eventreceiver.cpp index 270f7b7ee..e83f5a2ff 100644 --- a/src/plugins/codegeex/eventreceiver.cpp +++ b/src/plugins/codegeex/eventreceiver.cpp @@ -21,6 +21,7 @@ CodeGeeXReceiver::CodeGeeXReceiver(QObject *parent) eventHandleMap.insert(notifyManager.actionInvoked.name, std::bind(&CodeGeeXReceiver::processActionInvokedEvent, this, _1)); eventHandleMap.insert(project.openProject.name, std::bind(&CodeGeeXReceiver::processOpenProjectEvent, this, _1)); eventHandleMap.insert(uiController.switchToWidget.name, std::bind(&CodeGeeXReceiver::processSwitchToWidget, this, _1)); + eventHandleMap.insert(ai.LLMChanged.name, std::bind(&CodeGeeXReceiver::processLLMChanged, this)); } dpf::EventHandler::Type CodeGeeXReceiver::type() @@ -30,7 +31,7 @@ dpf::EventHandler::Type CodeGeeXReceiver::type() QStringList CodeGeeXReceiver::topics() { - return { T_MENU, editor.topic, notifyManager.topic, project.topic, uiController.topic }; + return { T_MENU, editor.topic, notifyManager.topic, project.topic, uiController.topic, ai.topic }; } void CodeGeeXReceiver::eventProcess(const dpf::Event &event) @@ -59,7 +60,7 @@ void CodeGeeXReceiver::processSelectionChangedEvent(const dpf::Event &event) int lineTo = event.property("lineTo").toInt(); int indexTo = event.property("indexTo").toInt(); Copilot::instance()->handleSelectionChanged(fileName, lineFrom, indexFrom, lineTo, indexTo); - emit CodeGeeXCallProxy::instance()->selectionChanged();; + emit CodeGeeXCallProxy::instance()->selectionChanged(); } void CodeGeeXReceiver::processInlineWidgetClosedEvent(const dpf::Event &event) @@ -70,9 +71,7 @@ void CodeGeeXReceiver::processInlineWidgetClosedEvent(const dpf::Event &event) void CodeGeeXReceiver::processActionInvokedEvent(const dpf::Event &event) { auto actId = event.property("actionId").toString(); - if (actId == "codegeex_login_default") - CodeGeeXManager::instance()->login(); - else if (actId == "ai_rag_install") + if (actId == "ai_rag_install") CodeGeeXManager::instance()->installConda(); } @@ -96,6 +95,11 @@ void CodeGeeXReceiver::processSwitchToWidget(const dpf::Event &event) windowService->showWidgetAtRightspace(MWNA_CODEGEEX); } +void CodeGeeXReceiver::processLLMChanged() +{ + emit CodeGeeXCallProxy::instance()->LLMsChanged(); +} + CodeGeeXCallProxy::CodeGeeXCallProxy() { } diff --git a/src/plugins/codegeex/eventreceiver.h b/src/plugins/codegeex/eventreceiver.h index cf2446775..52a0e6843 100644 --- a/src/plugins/codegeex/eventreceiver.h +++ b/src/plugins/codegeex/eventreceiver.h @@ -31,6 +31,7 @@ class CodeGeeXReceiver : public dpf::EventHandler, dpf::AutoEventHandlerRegister void processActionInvokedEvent(const dpf::Event &event); void processOpenProjectEvent(const dpf::Event &event); void processSwitchToWidget(const dpf::Event &event); + void processLLMChanged(); private: QHash> eventHandleMap; @@ -45,6 +46,7 @@ class CodeGeeXCallProxy : public QObject static CodeGeeXCallProxy *instance(); signals: + void LLMsChanged(); void selectionChanged(); void switchToWidget(const QString &name); }; diff --git a/src/plugins/codegeex/option/detailwidget.cpp b/src/plugins/codegeex/option/detailwidget.cpp index f6b7ef712..782d38cae 100644 --- a/src/plugins/codegeex/option/detailwidget.cpp +++ b/src/plugins/codegeex/option/detailwidget.cpp @@ -25,7 +25,6 @@ DWIDGET_USE_NAMESPACE -static const char *kCodeCompletion = "codeCompletion"; static const char *kGlobalLanguage = "globalLanguage"; static const char *kCommitsLanguage = "commitsLanguage"; @@ -33,7 +32,6 @@ class DetailWidgetPrivate { friend class DetailWidget; - DCheckBox *cbCodeCompletion = nullptr; DComboBox *globalLanguageBox = nullptr; DComboBox *commitsLanguageBox = nullptr; }; @@ -56,12 +54,6 @@ void DetailWidget::setupUi() QVBoxLayout *vLayout = new QVBoxLayout(this); setLayout(vLayout); - QHBoxLayout *completionLayout = new QHBoxLayout; - DLabel *completionLabel = new DLabel(QLabel::tr("Code Completion:"), this); - d->cbCodeCompletion = new DCheckBox(this); - completionLayout->addWidget(completionLabel); - completionLayout->addWidget(d->cbCodeCompletion); - QHBoxLayout *languageLayout = new QHBoxLayout; DLabel *languageLabel = new DLabel(QLabel::tr("Global Language Preference:"), this); d->globalLanguageBox = new DComboBox(this); @@ -78,7 +70,6 @@ void DetailWidget::setupUi() commitsLanguageLayout->addWidget(commitsLabel); commitsLanguageLayout->addWidget(d->commitsLanguageBox); - vLayout->addLayout(completionLayout); vLayout->addLayout(languageLayout); vLayout->addLayout(commitsLanguageLayout); vLayout->addStretch(); @@ -87,13 +78,11 @@ void DetailWidget::setupUi() bool DetailWidget::getControlValue(QMap &map) { CodeGeeXConfig config; - config.codeCompletionEnabled = d->cbCodeCompletion->isChecked(); - config.globalLanguage = d->globalLanguageBox->currentData().value(); - config.commitsLanguage = d->commitsLanguageBox->currentData().value(); + config.globalLanguage = d->globalLanguageBox->currentData().value(); + config.commitsLanguage = d->commitsLanguageBox->currentData().value(); dataToMap(config, map); - Copilot::instance()->setGenerateCodeEnabled(config.codeCompletionEnabled); - Copilot::instance()->setCommitsLocale(config.commitsLanguage == CodeGeeX::Zh ? "zh" : "en"); + Copilot::instance()->setCommitsLocale(config.commitsLanguage); CodeGeeXManager::instance()->setLocale(config.globalLanguage); return true; } @@ -104,14 +93,12 @@ void DetailWidget::setControlValue(const QMap &map) CodeGeeXConfig config; mapToData(map, config); - d->cbCodeCompletion->setChecked(config.codeCompletionEnabled); d->globalLanguageBox->setCurrentText(config.globalLanguage == CodeGeeX::Zh ? "简体中文" : "English"); d->commitsLanguageBox->setCurrentText(config.commitsLanguage == CodeGeeX::Zh ? "简体中文" : "English"); } bool DetailWidget::dataToMap(const CodeGeeXConfig &config, QMap &map) { - map.insert(kCodeCompletion, config.codeCompletionEnabled); map.insert(kGlobalLanguage, config.globalLanguage); map.insert(kCommitsLanguage, config.commitsLanguage); @@ -120,15 +107,12 @@ bool DetailWidget::dataToMap(const CodeGeeXConfig &config, QMap &map, CodeGeeXConfig &config) { - auto var = map.value(kCodeCompletion); - if (var.isValid()) - config.codeCompletionEnabled = var.toBool(); - var = map.value(kGlobalLanguage); + auto var = map.value(kGlobalLanguage); if (var.isValid()) - config.globalLanguage = var.value(); + config.globalLanguage = var.value(); var = map.value(kCommitsLanguage); if (var.isValid()) - config.commitsLanguage = var.value(); + config.commitsLanguage = var.value(); return true; } diff --git a/src/plugins/codegeex/option/detailwidget.h b/src/plugins/codegeex/option/detailwidget.h index eb19e83df..8d2395531 100644 --- a/src/plugins/codegeex/option/detailwidget.h +++ b/src/plugins/codegeex/option/detailwidget.h @@ -10,10 +10,8 @@ #include "common/widget/pagewidget.h" struct CodeGeeXConfig{ - bool codeCompletionEnabled = true; - CodeGeeX::locale globalLanguage = CodeGeeX::Zh; - CodeGeeX::locale commitsLanguage = CodeGeeX::Zh; - CodeGeeX::languageModel model = CodeGeeX::Lite; + CodeGeeX::Locale globalLanguage = CodeGeeX::Zh; + CodeGeeX::Locale commitsLanguage = CodeGeeX::Zh; }; class DetailWidgetPrivate; diff --git a/src/plugins/codegeex/widgets/askpagewidget.cpp b/src/plugins/codegeex/widgets/askpagewidget.cpp index 8e00ae9d1..965c94817 100644 --- a/src/plugins/codegeex/widgets/askpagewidget.cpp +++ b/src/plugins/codegeex/widgets/askpagewidget.cpp @@ -6,6 +6,9 @@ #include "intropage.h" #include "messagecomponent.h" #include "codegeexmanager.h" +#include "services/ai/aiservice.h" +#include "services/option/optionmanager.h" +#include "eventreceiver.h" #include #include @@ -24,6 +27,9 @@ // button height + margin static const int inputExtraHeight = 56; +static constexpr char* selectedLLM = "Selected_LLM"; +// todo: provide a unified category after modifying the plugin name. +static constexpr char* optionCategory = "AskPageEdit"; AskPageWidget::AskPageWidget(QWidget *parent) : DWidget(parent) @@ -97,10 +103,10 @@ void AskPageWidget::onDeleteBtnClicked() confirmDialog->insertButton(0, tr("Cancel", "button")); confirmDialog->insertButton(1, tr("Delete", "button"), false, DDialog::ButtonWarning); - connect(confirmDialog, &DDialog::buttonClicked, this, [](int index) { + connect(confirmDialog, &DDialog::buttonClicked, this, [=](int index) { if (index == 1) { CodeGeeXManager::instance()->deleteCurrentSession(); - CodeGeeXManager::instance()->cleanHistoryMessage(); + setIntroPage(); } }); @@ -114,14 +120,19 @@ void AskPageWidget::onHistoryBtnClicked() void AskPageWidget::onCreateNewBtnClicked() { - CodeGeeXManager::instance()->cleanHistoryMessage(); - CodeGeeXManager::instance()->createNewSession(); + //todo } -void AskPageWidget::onModelchanged(int index) +void AskPageWidget::onLLMChanged(int index) { - auto model = modelCb->itemData(index).value(); - CodeGeeXManager::instance()->setCurrentModel(model); + auto llmInfo = LLMInfo::fromVariantMap(modelCb->itemData(index).toMap()); + if (!llmInfo.modelName.isEmpty()) + CodeGeeXManager::instance()->onLLMChanged(llmInfo); + if (llmInfo.type != LLMType::ZHIPU_CODEGEEX) // function only codegeex can use + inputEdit->switchNetworkBtnVisible(false); + else + inputEdit->switchNetworkBtnVisible(true); + OptionManager::getInstance()->setValue(optionCategory, selectedLLM, llmInfo.toVariant()); } void AskPageWidget::initUI() @@ -182,6 +193,7 @@ void AskPageWidget::initInputWidget() historyBtn->setIcon(QIcon::fromTheme("codegeex_history")); historyBtn->setFixedSize(26, 26); historyBtn->setToolTip(tr("history sessions")); + historyBtn->hide(); // todo: Display after completion of functionality btnLayout->addWidget(historyBtn); createNewBtn = new DToolButton(this); @@ -193,9 +205,8 @@ void AskPageWidget::initInputWidget() modelCb = new QComboBox(this); modelCb->setFixedHeight(26); - modelCb->addItem(QIcon::fromTheme("codegeex_model_pro"), "Pro", CodeGeeX::languageModel::Pro); - modelCb->addItem(QIcon::fromTheme("codegeex_model_lite"), "Lite", CodeGeeX::languageModel::Lite); - modelCb->setFixedWidth(100); + modelCb->setMaximumWidth(200); + updateModelCb(); btnLayout->addWidget(modelCb); inputEdit = new InputEditWidget(inputWidget); @@ -222,7 +233,8 @@ void AskPageWidget::initConnection() connect(deleteBtn, &DToolButton::clicked, this, &AskPageWidget::onDeleteBtnClicked); connect(historyBtn, &DToolButton::clicked, this, &AskPageWidget::onHistoryBtnClicked); connect(createNewBtn, &DToolButton::clicked, this, &AskPageWidget::onCreateNewBtnClicked); - connect(modelCb, qOverload(&QComboBox::currentIndexChanged), this, &AskPageWidget::onModelchanged); + connect(modelCb, qOverload(&QComboBox::currentIndexChanged), this, &AskPageWidget::onLLMChanged); + connect(CodeGeeXCallProxy::instance(), &CodeGeeXCallProxy::LLMsChanged, this, &AskPageWidget::updateModelCb); connect(inputEdit->edit(), &DTextEdit::textChanged, this, [this]() { inputWidget->setFixedHeight(inputEdit->height() + inputExtraHeight); }); @@ -327,6 +339,27 @@ void AskPageWidget::showCustomWidget(QWidget *widget) waitComponets->setCustomWidget(widget); } +void AskPageWidget::updateModelCb() +{ + using namespace dpfservice; + auto aiSrv = dpfGetService(AiService); + auto allLLMs = aiSrv->getAllModel(); + + auto selectedLLMVariant = OptionManager::getInstance()->getValue(optionCategory, selectedLLM); + auto userSelectedLLMInfo = LLMInfo::fromVariantMap(selectedLLMVariant.toMap()); + auto currentSelectedName = modelCb->currentText(); + + modelCb->clear(); + for (auto llm : allLLMs) + modelCb->addItem(llm.icon, llm.modelName, llm.toVariant()); + + if (!userSelectedLLMInfo.modelName.isEmpty()) + modelCb->setCurrentText(userSelectedLLMInfo.modelName); + + if (!modelCb->currentText().isEmpty()) + CodeGeeXManager::instance()->onLLMChanged(LLMInfo::fromVariantMap(modelCb->currentData().toMap())); +} + void AskPageWidget::askQuestion(const QString &question) { CodeGeeXManager::instance()->sendMessage(question); diff --git a/src/plugins/codegeex/widgets/askpagewidget.h b/src/plugins/codegeex/widgets/askpagewidget.h index de3b1ffd9..a5542d305 100644 --- a/src/plugins/codegeex/widgets/askpagewidget.h +++ b/src/plugins/codegeex/widgets/askpagewidget.h @@ -47,9 +47,10 @@ public Q_SLOTS: void onDeleteBtnClicked(); void onHistoryBtnClicked(); void onCreateNewBtnClicked(); - void onModelchanged(int index); + void onLLMChanged(int index); void setInputText(const QString &prompt); void showCustomWidget(QWidget *widget); + void updateModelCb(); private: void initUI(); diff --git a/src/plugins/codegeex/widgets/codegeexwidget.cpp b/src/plugins/codegeex/widgets/codegeexwidget.cpp index 9a730f89c..c683b5a8c 100644 --- a/src/plugins/codegeex/widgets/codegeexwidget.cpp +++ b/src/plugins/codegeex/widgets/codegeexwidget.cpp @@ -24,37 +24,6 @@ CodeGeeXWidget::CodeGeeXWidget(QWidget *parent) initConnection(); } -void CodeGeeXWidget::onLoginSuccessed() -{ - auto mainLayout = qobject_cast(layout()); - if (mainLayout) { - QLayoutItem *item = nullptr; - while ((item = mainLayout->takeAt(0)) != nullptr) { - delete item->widget(); - delete item; - } - } - - initAskWidget(); - initHistoryWidget(); - CodeGeeXManager::instance()->createNewSession(); -} - -void CodeGeeXWidget::onLogOut() -{ - auto mainLayout = qobject_cast(layout()); - if (mainLayout) { - QLayoutItem *item = nullptr; - while ((item = mainLayout->takeAt(0)) != nullptr) { - delete item->widget(); - delete item; - } - } - - delete mainLayout; - initUI(); -} - void CodeGeeXWidget::onNewSessionCreated() { stackWidget->setCurrentIndex(1); @@ -74,8 +43,6 @@ void CodeGeeXWidget::onCloseHistoryWidget() void CodeGeeXWidget::onShowHistoryWidget() { - CodeGeeXManager::instance()->fetchSessionRecords(); - if (!historyWidget || !historyWidgetAnimation) return; @@ -103,68 +70,67 @@ void CodeGeeXWidget::initUI() { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setContentsMargins(0, 0, 0, 0); + auto mainLayout = new QVBoxLayout(this); - auto initLoginUI = [this]() { - auto mainLayout = new QVBoxLayout(this); - auto loginWidget = new DWidget(this); - loginWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - auto verticalLayout = new QVBoxLayout(loginWidget); - verticalLayout->setAlignment(Qt::AlignCenter); - verticalLayout->setContentsMargins(50, 0, 50, 50); - - auto label_icon = new DLabel(this); - label_icon->setPixmap(QIcon::fromTheme("codegeex_logo").pixmap(QSize(40, 26))); - label_icon->setAlignment(Qt::AlignCenter); - - verticalLayout->addWidget(label_icon); - - auto welcome_label = new DLabel(loginWidget); - welcome_label->setText(tr("Welcome to CodeGeeX"));//\nA must-have all-round AI tool for developers - welcome_label->setWordWrap(true); - welcome_label->setAlignment(Qt::AlignCenter); - - auto font = welcome_label->font(); - font.setPixelSize(14); - font.setWeight(500); - welcome_label->setFont(font); - - auto descrption_label = new DLabel(loginWidget); - descrption_label->setText(tr("A must-have all-round AI tool for developers")); - descrption_label->setWordWrap(true); - descrption_label->setAlignment(Qt::AlignCenter); - - font = descrption_label->font(); - font.setPixelSize(12); - font.setWeight(400); - descrption_label->setFont(font); - - verticalLayout->addSpacing(30); - verticalLayout->addWidget(welcome_label); - verticalLayout->addSpacing(5); - verticalLayout->addWidget(descrption_label); - - auto btnLayout = new QHBoxLayout; //make DSuggestBtn alignCenter - auto loginBtn = new DSuggestButton(loginWidget); - loginBtn->setText(tr("Go to login")); - connect(loginBtn, &DSuggestButton::clicked, this, [=] { - CodeGeeXManager::instance()->login(); - }); - - btnLayout->addWidget(loginBtn, Qt::AlignHCenter); - - verticalLayout->addSpacing(30); - verticalLayout->addLayout(btnLayout, Qt::AlignCenter); - - mainLayout->addWidget(loginWidget, 1, Qt::AlignVCenter); - }; - initLoginUI(); + initAskWidget(); + initHistoryWidget(); + onNewSessionCreated(); // todo: modifed + +// auto initLoginUI = [this]() { +// auto mainLayout = new QVBoxLayout(this); +// auto loginWidget = new DWidget(this); +// loginWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); +// auto verticalLayout = new QVBoxLayout(loginWidget); +// verticalLayout->setAlignment(Qt::AlignCenter); +// verticalLayout->setContentsMargins(50, 0, 50, 50); + +// auto label_icon = new DLabel(this); +// label_icon->setPixmap(QIcon::fromTheme("codegeex_logo").pixmap(QSize(40, 26))); +// label_icon->setAlignment(Qt::AlignCenter); + +// verticalLayout->addWidget(label_icon); + +// auto welcome_label = new DLabel(loginWidget); +// welcome_label->setText(tr("Welcome to CodeGeeX"));//\nA must-have all-round AI tool for developers +// welcome_label->setWordWrap(true); +// welcome_label->setAlignment(Qt::AlignCenter); + +// auto font = welcome_label->font(); +// font.setPixelSize(14); +// font.setWeight(500); +// welcome_label->setFont(font); + +// auto descrption_label = new DLabel(loginWidget); +// descrption_label->setText(tr("A must-have all-round AI tool for developers")); +// descrption_label->setWordWrap(true); +// descrption_label->setAlignment(Qt::AlignCenter); + +// font = descrption_label->font(); +// font.setPixelSize(12); +// font.setWeight(400); +// descrption_label->setFont(font); + +// verticalLayout->addSpacing(30); +// verticalLayout->addWidget(welcome_label); +// verticalLayout->addSpacing(5); +// verticalLayout->addWidget(descrption_label); + +// auto btnLayout = new QHBoxLayout; //make DSuggestBtn alignCenter +// auto loginBtn = new DSuggestButton(loginWidget); +// loginBtn->setText(tr("Go to login")); + +// btnLayout->addWidget(loginBtn, Qt::AlignHCenter); + +// verticalLayout->addSpacing(30); +// verticalLayout->addLayout(btnLayout, Qt::AlignCenter); + +// mainLayout->addWidget(loginWidget, 1, Qt::AlignVCenter); +// }; +// initLoginUI(); } void CodeGeeXWidget::initConnection() { - connect(CodeGeeXManager::instance(), &CodeGeeXManager::loginSuccessed, this, &CodeGeeXWidget::onLoginSuccessed); - connect(CodeGeeXManager::instance(), &CodeGeeXManager::logoutSuccessed, this, &CodeGeeXWidget::onLogOut); - connect(CodeGeeXManager::instance(), &CodeGeeXManager::createdNewSession, this, &CodeGeeXWidget::onNewSessionCreated); } void CodeGeeXWidget::initAskWidget() diff --git a/src/plugins/codegeex/widgets/codegeexwidget.h b/src/plugins/codegeex/widgets/codegeexwidget.h index ded607d43..f76eadb31 100644 --- a/src/plugins/codegeex/widgets/codegeexwidget.h +++ b/src/plugins/codegeex/widgets/codegeexwidget.h @@ -20,7 +20,6 @@ class CodeGeeXWidget : public DTK_WIDGET_NAMESPACE::DFrame explicit CodeGeeXWidget(QWidget *parent = nullptr); public Q_SLOTS: - void onLoginSuccessed(); void onLogOut(); void onNewSessionCreated(); void onCloseHistoryWidget(); diff --git a/src/plugins/codegeex/widgets/inlinechatwidget.cpp b/src/plugins/codegeex/widgets/inlinechatwidget.cpp index b8033f15b..16d6e7b88 100644 --- a/src/plugins/codegeex/widgets/inlinechatwidget.cpp +++ b/src/plugins/codegeex/widgets/inlinechatwidget.cpp @@ -4,9 +4,7 @@ #include "inlinechatwidget.h" #include "inputeditwidget.h" -#include "copilot.h" #include "diff_match_patch.h" -#include "codegeex/copilotapi.h" #include "codegeexmanager.h" #include "services/editor/editorservice.h" @@ -30,6 +28,16 @@ constexpr char kVisibleProperty[] { "VisibleProperty" }; constexpr char kInlineChatUrl[] = "https://codegeex.cn/prod/code/chatCodeSseV3/chat?stream=false"; +static QString systemenPrompt = "你会为用户回答关于编程、代码、计算机方面的任何问题,并提供格式规范、可以执行、准确安全的代码,并在必要时提供详细的解释。任务:根据用户的Command作答。\n" +"\nYou are working on a task with User in a file and User send a message to you. `【cursor】` indicate the current position of your cursor, delete it after modification.\n\nYour Workflow:\n" +"Step 1: You should working on the command step-by-step:\n1. Does the command is about to write program or comments in programming task? Write out the type of the command: (Choose: Chat / Programming).\n- **Chat command**: When your aim to reply through explanations, reminders, or requests for additional information. \n- **Programming command**: The user command is clear and requires you to write program or comments. \n" +"Step 2: If it's a chat command, provide a thoughtful and clear response {language} directly.\n" +"Step 3: If it requires programming, you should complete the Task according to the given file. \na. Understand the message in order to tackle it correctly: is the task about coding or debugging?\n- For coding, add new code within the task to execute the user's instructions correctly. \n- For debugging, improve the code to fix the bug, write the reasons for the changes within your code as comments.\nb. You should complete the task according to user request and comment in Chinese within your code to indicate changes. \nc. You should place only the finished task in a single block as output without explain!!!\n\nReply in the template below:\nCommand Type: (Chat / Programming)\nResponse: (Your response / Finished code in one block)\n" +"User`s question: %1\n" +"Selected code: %2\n" +"Related context about this question(Do not directly quote when answering):%3\n" +"Command:%4"; + using namespace CodeGeeX; using namespace dpfservice; DWIDGET_USE_NAMESPACE @@ -99,7 +107,7 @@ class InlineChatWidgetPrivate : public QObject void handleTextChanged(); void handleSubmitEdit(); void handleQuickQuestion(); - void handleAskFinished(CopilotApi::ResponseType type, const QString &response, const QString &dstLang); + void handleAskFinished(const QString &response); void handleAccept(); void handleReject(); @@ -140,7 +148,7 @@ class InlineChatWidgetPrivate : public QObject CodeInfo codeInfo; State state { None }; State prevState { None }; - CopilotApi copilotApi; + AbstractLLM *inlineChatLLM { nullptr }; int deleteMarker { -1 }; int insertMarker { -1 }; int selectionMarker { -1 }; @@ -244,8 +252,6 @@ void InlineChatWidgetPrivate::initConnection() connect(acceptBtn, &QAbstractButton::clicked, this, &InlineChatWidgetPrivate::handleAccept); connect(rejectBtn, &QAbstractButton::clicked, this, &InlineChatWidgetPrivate::handleReject); connect(stopBtn, &QAbstractButton::clicked, this, &InlineChatWidgetPrivate::handleStop); - - connect(&copilotApi, &CopilotApi::response, this, &InlineChatWidgetPrivate::handleAskFinished); } QAbstractButton *InlineChatWidgetPrivate::createButton(const QString &name, ButtonType type, int flags) @@ -346,6 +352,7 @@ void InlineChatWidgetPrivate::handleSubmitEdit() } setState(SubmitStart); + if (!askForCodeGeeX()) { qWarning() << "Failed to ask CodeGeeX"; setState(Original); @@ -366,15 +373,8 @@ void InlineChatWidgetPrivate::handleQuickQuestion() } } -void InlineChatWidgetPrivate::handleAskFinished(CopilotApi::ResponseType type, const QString &response, const QString &dstLang) +void InlineChatWidgetPrivate::handleAskFinished(const QString &response) { - Q_UNUSED(dstLang) - - if (type != CopilotApi::inline_chat) { - setState(Original); - return; - } - if (state == QuestionStart) { auto answer = answerLabel->text(); answerLabel->setText(answer.append(response)); @@ -444,15 +444,12 @@ void InlineChatWidgetPrivate::handleAccept() if (!replaceText.endsWith('\n')) replaceText.append('\n'); - bool enabled = Copilot::instance()->getGenerateCodeEnabled(); - Copilot::instance()->setGenerateCodeEnabled(false); int endLineOffset = chatInfo.tempText.count('\n') - chatInfo.originalText.count('\n') - 1; Edit::Range replaceRange = chatInfo.originalRange; replaceRange.end.line += endLineOffset; editSrv->replaceRange(chatInfo.fileName, replaceRange, replaceText); chatInfo.tempText.clear(); handleClose(); - Copilot::instance()->setGenerateCodeEnabled(enabled); } void InlineChatWidgetPrivate::handleReject() @@ -462,8 +459,6 @@ void InlineChatWidgetPrivate::handleReject() if (!replaceText.endsWith('\n')) replaceText.append('\n'); - bool enabled = Copilot::instance()->getGenerateCodeEnabled(); - Copilot::instance()->setGenerateCodeEnabled(false); int endLineOffset = chatInfo.tempText.count('\n') - chatInfo.originalText.count('\n') - 1; chatInfo.tempText.clear(); Edit::Range replaceRange = chatInfo.originalRange; @@ -471,7 +466,6 @@ void InlineChatWidgetPrivate::handleReject() editSrv->replaceRange(chatInfo.fileName, replaceRange, replaceText); editSrv->setRangeBackgroundColor(chatInfo.fileName, chatInfo.originalRange.start.line, chatInfo.originalRange.end.line, selectionMarker); - Copilot::instance()->setGenerateCodeEnabled(enabled); } if (insertMarker != -1) @@ -497,7 +491,7 @@ void InlineChatWidgetPrivate::handleStop() if (!watcher->isFinished()) watcher->cancel(); } - Q_EMIT copilotApi.requestStop(); + inlineChatLLM->cancel(); } void InlineChatWidgetPrivate::handleCreatePromptFinished() @@ -505,16 +499,13 @@ void InlineChatWidgetPrivate::handleCreatePromptFinished() auto watcher = static_cast *>(sender()); if (!watcher->isCanceled()) { const auto &prompt = watcher->result(); - InlineChatInfo info; - info.fileName = chatInfo.fileName; - info.is_ast = true; - info.commandType = state == QuestionStart ? InlineChatInfo::Chat : InlineChatInfo::Programing; - info.contextCode = addLineNumber(editSrv->fileText(info.fileName), 1); - const auto &code = createFormatCode(info.fileName, chatInfo.originalText, chatInfo.originalRange); - info.selectedCode = code; - - copilotApi.setModel(Copilot::instance()->getCurrentModel()); - copilotApi.postInlineChat(kInlineChatUrl, prompt, info, Copilot::instance()->getLocale()); + inlineChatLLM->setStream(false); + inlineChatLLM->request(prompt, [=](const QString &data, AbstractLLM::ResponseState state){ + if (state == AbstractLLM::Receiving || (state == AbstractLLM::Success && !data.isEmpty())) + handleAskFinished(data); + else if (state == AbstractLLM::Failed) + setState(Original); + }); } futureWatcherList.removeAll(watcher); @@ -617,8 +608,6 @@ void InlineChatWidgetPrivate::processGeneratedData(const QString &data) tempText.append(diff.text); } - bool enabled = Copilot::instance()->getGenerateCodeEnabled(); - Copilot::instance()->setGenerateCodeEnabled(false); chatInfo.tempText = tempText; editSrv->replaceRange(chatInfo.fileName, chatInfo.originalRange, tempText); auto iter = chatInfo.operationRange.cbegin(); @@ -627,7 +616,6 @@ void InlineChatWidgetPrivate::processGeneratedData(const QString &data) iter.key() == DELETE ? editSrv->setRangeBackgroundColor(chatInfo.fileName, range.start.line, range.end.line, deleteMarker) : editSrv->setRangeBackgroundColor(chatInfo.fileName, range.start.line, range.end.line, insertMarker); } - Copilot::instance()->setGenerateCodeEnabled(enabled); } void InlineChatWidgetPrivate::updateButtonIcon() @@ -641,9 +629,11 @@ void InlineChatWidgetPrivate::updateButtonIcon() QString InlineChatWidgetPrivate::createPrompt(const QString &question, bool useChunk) { - QStringList prompt; - prompt << question; + QString prompt = systemenPrompt; + QStringList context = {""}; + auto code = createFormatCode(chatInfo.fileName, chatInfo.originalText, chatInfo.originalRange); + auto fileText = editSrv->fileText(chatInfo.fileName); if (useChunk) { QString workspace = chatInfo.fileName; ProjectService *prjSrv = dpfGetService(ProjectService); @@ -655,20 +645,18 @@ QString InlineChatWidgetPrivate::createPrompt(const QString &question, bool useC } } - prompt << "回答内容不要使用下面的参考内容"; - prompt << "\n你可以使用下面这些文件和代码内容进行参考,但只针对上面这段代码进行回答"; - QString query = "问题:%1\n内容:```%2```"; - auto result = CodeGeeXManager::instance()->query(workspace, query.arg(question, chatInfo.originalText), 5); + auto result = CodeGeeXManager::instance()->query(workspace, code, 5); QJsonArray chunks = result["Chunks"].toArray(); - prompt << "代码:\n```"; + context << ""; for (auto chunk : chunks) { - prompt << chunk.toObject()["fileName"].toString(); - prompt << chunk.toObject()["content"].toString(); + context << chunk.toObject()["fileName"].toString(); + context << chunk.toObject()["content"].toString(); } - prompt << "```"; + context << ""; } + QString command = state == QuestionStart ? "Chat" : "Programing"; - return prompt.join('\n'); + return prompt.arg(question, code, context.join('\n'), command); } Edit::Range InlineChatWidgetPrivate::calculateTextRange(const QString &fileName, const Edit::Position &pos) @@ -754,6 +742,13 @@ InlineChatWidget::~InlineChatWidget() delete d; } +void InlineChatWidget::setLLM(AbstractLLM *llm) +{ + if (!llm) + return; + d->inlineChatLLM = llm; +} + void InlineChatWidget::start() { reset(); diff --git a/src/plugins/codegeex/widgets/inlinechatwidget.h b/src/plugins/codegeex/widgets/inlinechatwidget.h index 8bfdf44ba..e3d5712d4 100644 --- a/src/plugins/codegeex/widgets/inlinechatwidget.h +++ b/src/plugins/codegeex/widgets/inlinechatwidget.h @@ -6,6 +6,7 @@ #define INLINECHATWIDGET_H #include +#include "base/ai/abstractllm.h" class InlineChatWidgetPrivate; class InlineChatWidget : public QWidget @@ -14,6 +15,7 @@ class InlineChatWidget : public QWidget public: explicit InlineChatWidget(QWidget *parent = nullptr); ~InlineChatWidget(); + void setLLM(AbstractLLM *llm); public Q_SLOTS: void start(); diff --git a/src/plugins/codegeex/widgets/inputeditwidget.cpp b/src/plugins/codegeex/widgets/inputeditwidget.cpp index a35578716..f49547813 100644 --- a/src/plugins/codegeex/widgets/inputeditwidget.cpp +++ b/src/plugins/codegeex/widgets/inputeditwidget.cpp @@ -624,6 +624,15 @@ void InputEditWidget::accept(const QModelIndex &index) CodeGeeXManager::instance()->setReferenceFiles(d->selectedFiles); } +void InputEditWidget::switchNetworkBtnVisible(bool visible) +{ + d->netWorkBtn->setVisible(visible); + if (!visible) { + d->netWorkBtn->setChecked(false); + CodeGeeXManager::instance()->connectToNetWork(false); + } +} + // use to restore tag, : remove tag then Ctrl+z void InputEditWidget::onTagAdded(const QString &text) { diff --git a/src/plugins/codegeex/widgets/inputeditwidget.h b/src/plugins/codegeex/widgets/inputeditwidget.h index 7d2590e72..17892705a 100644 --- a/src/plugins/codegeex/widgets/inputeditwidget.h +++ b/src/plugins/codegeex/widgets/inputeditwidget.h @@ -68,6 +68,8 @@ class InputEditWidget : public DTK_WIDGET_NAMESPACE::DFrame void popupReference(); void accept(const QModelIndex &index); + void switchNetworkBtnVisible(bool visible); + signals: void pressedEnter(); void messageSended(); diff --git a/src/plugins/codegeex/widgets/intropage.cpp b/src/plugins/codegeex/widgets/intropage.cpp index 326c54396..68f624a8e 100644 --- a/src/plugins/codegeex/widgets/intropage.cpp +++ b/src/plugins/codegeex/widgets/intropage.cpp @@ -122,10 +122,6 @@ void IntroPage::initLogoutButton() QHBoxLayout *hlayout = new QHBoxLayout; auto logoutButton = new DCommandLinkButton(tr("logout")); - connect(logoutButton, &DCommandLinkButton::clicked, this, []() { - CodeGeeXManager::instance()->logout(); - }); - hlayout->addWidget(logoutButton); hlayout->setAlignment(Qt::AlignHCenter); diff --git a/src/plugins/codegeex/widgets/messagecomponent.cpp b/src/plugins/codegeex/widgets/messagecomponent.cpp index 7fdd6a219..7116bd218 100644 --- a/src/plugins/codegeex/widgets/messagecomponent.cpp +++ b/src/plugins/codegeex/widgets/messagecomponent.cpp @@ -207,7 +207,7 @@ void MessageComponent::initConnect() CodeGeeXManager::instance()->setMessage(messageData.messageData()); }); connect(CodeGeeXManager::instance(), &CodeGeeXManager::crawledWebsite, this, - [=](const QString &msgID, const QList &websites) { + [=](const QString &msgID, const QList &websites) { Q_UNUSED(msgID); if (!finished) this->websites = websites; diff --git a/src/plugins/codegeex/widgets/messagecomponent.h b/src/plugins/codegeex/widgets/messagecomponent.h index 600ec5b48..ae1679bf0 100644 --- a/src/plugins/codegeex/widgets/messagecomponent.h +++ b/src/plugins/codegeex/widgets/messagecomponent.h @@ -68,7 +68,7 @@ class MessageComponent : public DFrame MessageData messageData; UpdateState currentUpdateState = Label; - QList websites; + QList websites; }; #endif // MESSAGECOMPONENT_H diff --git a/src/plugins/codegeex/widgets/sessionrecorditem.cpp b/src/plugins/codegeex/widgets/sessionrecorditem.cpp index 25678ea6a..c3bcf6235 100644 --- a/src/plugins/codegeex/widgets/sessionrecorditem.cpp +++ b/src/plugins/codegeex/widgets/sessionrecorditem.cpp @@ -39,8 +39,7 @@ void SessionRecordItem::onDeleteButtonClicked() void SessionRecordItem::onRecordClicked() { - CodeGeeXManager::instance()->fetchMessageList(talkId); - + // todo show chat details Q_EMIT closeHistoryWidget(); } diff --git a/src/plugins/console/generateinput.cpp b/src/plugins/console/generateinput.cpp index b044295df..f018057ba 100644 --- a/src/plugins/console/generateinput.cpp +++ b/src/plugins/console/generateinput.cpp @@ -17,18 +17,18 @@ DWIDGET_USE_NAMESPACE class GenerateInputPrivate { public: - QBuffer *pipe { nullptr }; DLineEdit *edit { nullptr }; DSuggestButton *confirmBtn { nullptr }; DIconButton *closeBtn { nullptr }; DSpinner *spinner { nullptr }; + AbstractLLM *llm { nullptr }; }; GenerateInput::GenerateInput(QWidget *parent) : QWidget(parent), d(new GenerateInputPrivate) { initUi(); - initPipe(); + initLLM(); initConnect(); } @@ -60,12 +60,26 @@ void GenerateInput::initUi() layout->addWidget(d->closeBtn); } -void GenerateInput::initPipe() +void GenerateInput::initLLM() { - d->pipe = new QBuffer(this); - connect(d->pipe, &QBuffer::aboutToClose, this , [=](){ - d->pipe->seek(0); - auto response = QString(d->pipe->readAll()); + using namespace dpfservice; + auto aiSrv = dpfGetService(AiService); + LLMInfo liteModel; + auto liteLLMInfo = aiSrv->getCodeGeeXLLMPro(); + d->llm = aiSrv->getLLM(liteLLMInfo); + d->llm->setStream(false); + connect(d->llm, &AbstractLLM::dataReceived, this, [=](const QString &data, AbstractLLM::ResponseState state){ + if (state == AbstractLLM::Failed) { + QString err = ""; + auto valid = d->llm->checkValid(&err); + if (!valid){ + switchState(false); + emit commandGenerated(err); + } else { + emit commandGenerated(tr("Please try again later")); + } + } + auto response = data; QString results; QRegularExpression regex(R"(```.*\n((.*\n)*?.*)\n\s*```)"); @@ -106,11 +120,8 @@ void GenerateInput::onGenerate() } switchState(true); - d->pipe->open(QIODevice::ReadWrite); QString prompt = "你是一个智能终端机器人,你的工作环境是deepin-os/UOS/Linux,你的任务是根据描述生成可以直接使用的终端命令,不要进行额外的回答。描述是:" + text; - using namespace dpfservice; - auto aiSrv = dpfGetService(AiService); - aiSrv->askQuestion(prompt, d->pipe); + d->llm->request(prompt); } void GenerateInput::switchState(bool generating) @@ -118,7 +129,6 @@ void GenerateInput::switchState(bool generating) if (generating) { d->spinner->start(); d->spinner->show(); - d->pipe->setData(""); // reset pipe d->spinner->move(d->edit->rect().bottomRight() - QPoint(38, 16)); d->edit->setEnabled(false); d->confirmBtn->setEnabled(false); diff --git a/src/plugins/console/generateinput.h b/src/plugins/console/generateinput.h index 77ce86e11..21269e6b7 100644 --- a/src/plugins/console/generateinput.h +++ b/src/plugins/console/generateinput.h @@ -24,7 +24,7 @@ public slots: private: void initUi(); - void initPipe(); + void initLLM(); void initConnect(); GenerateInputPrivate *d; }; diff --git a/src/services/ai/aiservice.h b/src/services/ai/aiservice.h index dbf8f0fff..192484937 100644 --- a/src/services/ai/aiservice.h +++ b/src/services/ai/aiservice.h @@ -10,6 +10,7 @@ #include #include +#include enum LLMType { OPENAI, @@ -33,6 +34,7 @@ struct LLMInfo QString modelName = ""; QString modelPath = ""; QString apikey = ""; + QIcon icon; LLMType type; bool operator==(const LLMInfo &info) const { @@ -48,6 +50,7 @@ struct LLMInfo map["modelPath"] = modelPath; map["apikey"] = apikey; map["type"] = static_cast(type); + map["icon"] = icon.name(); return QVariant(map); } static LLMInfo fromVariantMap(const QVariantMap &map) @@ -57,6 +60,7 @@ struct LLMInfo info.modelPath = map["modelPath"].toString(); info.apikey = map["apikey"].toString(); info.type = static_cast(map["type"].toInt()); + info.icon = QIcon::fromTheme(map["icon"].toString()); return info; } }; @@ -81,15 +85,17 @@ class SERVICE_EXPORT AiService final : public dpf::PluginService, dpf::AutoServi } - // codegeex - DPF_INTERFACE(bool, available); - DPF_INTERFACE(void, askQuestion, const QString &prompt, QIODevice *pipe); // pipe recevice data from ai - DPF_INTERFACE(void, askQuestionWithHistory, const QString &prompt, const QMultiMap history, QIODevice *pipe); - // custom model DPF_INTERFACE(AbstractLLM *, getLLM, const LLMInfo &info); DPF_INTERFACE(QList, getAllModel); + DPF_INTERFACE(bool, registerLLM, const QString &name, const LLMInfo &info); + + // Ai chat + DPF_INTERFACE(void, chatWithAi, const QString &message); + // default Model + DPF_INTERFACE(LLMInfo, getCodeGeeXLLMLite); + DPF_INTERFACE(LLMInfo, getCodeGeeXLLMPro); // rag DPF_INTERFACE(void, generateRag, const QString &projectPath);