From d3a525879cb9e36dde5476f5aa3896b2b21ad177 Mon Sep 17 00:00:00 2001 From: liuyangming Date: Thu, 9 Nov 2023 18:05:21 +0800 Subject: [PATCH] feat: [CodeGeeX]Widgets of CodeGeeX add widgets of CodeGeeX plugin Log: Add CodeGeeX UI Task: --- src/plugins/codegeex/CMakeLists.txt | 32 +--- src/plugins/codegeex/askpage/askpage.cpp | 2 +- src/plugins/codegeex/codegeex.cpp | 9 +- src/plugins/codegeex/codegeex/askapi.cpp | 5 +- src/plugins/codegeex/codegeex/askapi.h | 2 +- src/plugins/codegeex/codegeexmanager.cpp | 158 ++++++++++++++++++ src/plugins/codegeex/codegeexmanager.h | 57 +++++++ src/plugins/codegeex/data/messagedata.cpp | 60 +++++++ src/plugins/codegeex/data/messagedata.h | 42 +++++ .../codegeex/widgets/askpagewidget.cpp | 129 ++++++++++++++ src/plugins/codegeex/widgets/askpagewidget.h | 57 +++++++ .../codegeex/widgets/codeeditcomponent.cpp | 128 ++++++++++++++ .../codegeex/widgets/codeeditcomponent.h | 54 ++++++ .../codegeex/widgets/codegeexwidget.cpp | 97 +++++++++++ src/plugins/codegeex/widgets/codegeexwidget.h | 39 +++++ src/plugins/codegeex/widgets/intropage.cpp | 102 +++++++++++ src/plugins/codegeex/widgets/intropage.h | 33 ++++ .../codegeex/widgets/messagecomponent.cpp | 123 ++++++++++++++ .../codegeex/widgets/messagecomponent.h | 50 ++++++ .../widgets/translationpagewidget.cpp | 67 ++++++++ .../codegeex/widgets/translationpagewidget.h | 20 +++ 21 files changed, 1235 insertions(+), 31 deletions(-) create mode 100644 src/plugins/codegeex/codegeexmanager.cpp create mode 100644 src/plugins/codegeex/codegeexmanager.h create mode 100644 src/plugins/codegeex/data/messagedata.cpp create mode 100644 src/plugins/codegeex/data/messagedata.h create mode 100644 src/plugins/codegeex/widgets/askpagewidget.cpp create mode 100644 src/plugins/codegeex/widgets/askpagewidget.h create mode 100644 src/plugins/codegeex/widgets/codeeditcomponent.cpp create mode 100644 src/plugins/codegeex/widgets/codeeditcomponent.h create mode 100644 src/plugins/codegeex/widgets/codegeexwidget.cpp create mode 100644 src/plugins/codegeex/widgets/codegeexwidget.h create mode 100644 src/plugins/codegeex/widgets/intropage.cpp create mode 100644 src/plugins/codegeex/widgets/intropage.h create mode 100644 src/plugins/codegeex/widgets/messagecomponent.cpp create mode 100644 src/plugins/codegeex/widgets/messagecomponent.h create mode 100644 src/plugins/codegeex/widgets/translationpagewidget.cpp create mode 100644 src/plugins/codegeex/widgets/translationpagewidget.h diff --git a/src/plugins/codegeex/CMakeLists.txt b/src/plugins/codegeex/CMakeLists.txt index b777233bf..88866e5fc 100644 --- a/src/plugins/codegeex/CMakeLists.txt +++ b/src/plugins/codegeex/CMakeLists.txt @@ -2,35 +2,17 @@ cmake_minimum_required(VERSION 3.0.2) project(plugin-codegeex) -set(CXX_CPP - codegeex.cpp - eventreceiver.cpp - codegeex/askapi.cpp - codegeex/copilotapi.cpp - askpage/askpage.cpp - option/codegeexoptionwidget.cpp - option/detailwidget.cpp - option/optioncodegeexgenerator.cpp - copilot.cpp - codegeex.json - ) - -set(CXX_H - codegeex.h - eventreceiver.h - codegeex/askapi.h - codegeex/copilotapi.h - copilot.h - askpage/askpage.h - option/codegeexoptionwidget.h - option/detailwidget.h - option/optioncodegeexgenerator.h +FILE(GLOB CODEGEEX_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*.json" ) add_library(${PROJECT_NAME} SHARED - ${CXX_H} - ${CXX_CPP} + ${CODEGEEX_FILES} codegeex.qrc ) diff --git a/src/plugins/codegeex/askpage/askpage.cpp b/src/plugins/codegeex/askpage/askpage.cpp index db730342d..4561d4905 100644 --- a/src/plugins/codegeex/askpage/askpage.cpp +++ b/src/plugins/codegeex/askpage/askpage.cpp @@ -50,7 +50,7 @@ AskPage::AskPage(QWidget *parent) : QWidget(parent) connect(timer, SIGNAL(timeout()), this, SLOT(queryLoginState())); timer->start(1000); - connect(&askApi, &AskApi::response, [this](const QString &response, const QString &event){ + connect(&askApi, &AskApi::response, [this](const QString &msgID, const QString &response, const QString &event){ int responseSize = response.size(); if (responseSize < totalResponseSize || responseSize == 0) return; diff --git a/src/plugins/codegeex/codegeex.cpp b/src/plugins/codegeex/codegeex.cpp index c6da5f2fa..d358f95bc 100644 --- a/src/plugins/codegeex/codegeex.cpp +++ b/src/plugins/codegeex/codegeex.cpp @@ -5,6 +5,8 @@ #include "codegeex.h" #include "askpage/askpage.h" #include "option/optioncodegeexgenerator.h" +#include "widgets/codegeexwidget.h" +#include "codegeexmanager.h" #include "common/common.h" #include "services/window/windowservice.h" @@ -31,8 +33,11 @@ bool CodeGeex::start() // Add widget to left bar if (windowService->addCentralNavigation) { - auto askPage = new AskPage(); - windowService->addWidgetWorkspace(title, new AbstractWidget(askPage)); +// auto askPage = new AskPage(); +// windowService->addWidgetWorkspace(title, new AbstractWidget(askPage)); + + auto codeGeeXWidget = new CodeGeeXWidget(); + windowService->addWidgetWorkspace(title, new AbstractWidget(codeGeeXWidget)); } } diff --git a/src/plugins/codegeex/codegeex/askapi.cpp b/src/plugins/codegeex/codegeex/askapi.cpp index 3985993f3..99db7db05 100644 --- a/src/plugins/codegeex/codegeex/askapi.cpp +++ b/src/plugins/codegeex/codegeex/askapi.cpp @@ -198,17 +198,18 @@ void AskApi::processResponse(QNetworkReply *reply) QStringList lines = replyMsg.split('\n'); QByteArray data; QString event; + QString id; for (const auto& line : lines) { if (line.startsWith("event:add")) { event = "add"; } else if (line.startsWith("event:finish")) { event = "finish"; } else if (line.startsWith("id:")) { - continue; + id = line.mid(3); } else if (line.startsWith("data:")) { data += line.mid(5) + '\n'; } else if (line == "") { - emit response(data, event); + emit response(id, data, event); data.clear(); } } diff --git a/src/plugins/codegeex/codegeex/askapi.h b/src/plugins/codegeex/codegeex/askapi.h index 2b6176f16..737a5f429 100644 --- a/src/plugins/codegeex/codegeex/askapi.h +++ b/src/plugins/codegeex/codegeex/askapi.h @@ -71,7 +71,7 @@ class AskApi : public QObject signals: void loginState(LoginState loginState); - void response(const QString &response, const QString &event); + void response(const QString &msgID, const QString &response, const QString &event); void getSessionListResult(const QVector &records); void getChatRecordResult(const QVector &record); void sessionDeleted(const QStringList &talkId, bool isSuccessful); diff --git a/src/plugins/codegeex/codegeexmanager.cpp b/src/plugins/codegeex/codegeexmanager.cpp new file mode 100644 index 000000000..60a57f4fc --- /dev/null +++ b/src/plugins/codegeex/codegeexmanager.cpp @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "codegeexmanager.h" +#include "common/util/custompaths.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *kUrlSSEChat = "https://codegeex.cn/prod/code/chatGlmSse/chat"; +static const char *kUrlNewSession = "https://codegeex.cn/prod/code/chatGlmTalk/insert"; + +using namespace CodeGeeX; + +CodeGeeXManager *CodeGeeXManager::instance() +{ + static CodeGeeXManager ins; + return &ins; +} + +void CodeGeeXManager::login() +{ + if (sessionId.isEmpty() || userId.isEmpty()) { + sessionId = uuid(); + userId = uuid(); + saveConfig(sessionId, userId); + } + + QString machineId = QSysInfo::machineUniqueId(); + askApi.sendLoginRequest(sessionId, machineId, userId); + + queryLoginState(); +} + +void CodeGeeXManager::saveConfig(const QString &sessionId, const QString &userId) +{ + QJsonObject config; + config["sessionId"] = sessionId; + config["userId"] = userId; + + QJsonDocument document(config); + + QFile file(configFilePath()); + file.open(QIODevice::WriteOnly); + file.write(document.toJson()); + file.close(); +} + +void CodeGeeXManager::loadConfig() +{ + QFile file(configFilePath()); + if (!file.exists()) + return; + + file.open(QIODevice::ReadOnly); + QString data = QString::fromUtf8(file.readAll()); + file.close(); + + QJsonDocument document = QJsonDocument::fromJson(data.toUtf8()); + QJsonObject config = document.object(); + if (!config.empty()) { + sessionId = config["sessionId"].toString(); + userId = config["userId"].toString(); + } +} + +void CodeGeeXManager::sendMessage(const QString &prompt) +{ + QString askId = "User" + QString::number(QDateTime::currentMSecsSinceEpoch()); + MessageData msgData(askId, MessageData::Ask); + msgData.updateData(prompt); + Q_EMIT requestMessageUpdate(msgData); + + QMultiMap history {}; + for (auto msgData : curSessionMsg) { + history.insert(msgData.messageID(), msgData.messageData()); + } + QString machineId = QSysInfo::machineUniqueId(); + askApi.postSSEChat(kUrlSSEChat, sessionId, prompt, machineId, history); +} + +void CodeGeeXManager::onResponse(const QString &msgID, const QString &data, const QString &event) +{ + if (msgID.isEmpty()) + return; + +// qInfo() << "resp msg:" << msgID << data << event; + if (!curSessionMsg.contains(msgID)) + curSessionMsg.insert(msgID, MessageData(msgID, MessageData::Anwser)); + + if (!data.isEmpty()) { + curSessionMsg[msgID].updateData(data); + Q_EMIT requestMessageUpdate(curSessionMsg[msgID]); + } + + if (event == "finish") { + + } else if (event == "add"){ + + } +} + +void CodeGeeXManager::recevieLoginState(AskApi::LoginState loginState) +{ + if (loginState == AskApi::LoginState::kLoginFailed) { + qWarning() << "CodeGeeX login failed!"; + // switch to login ui. + } else if (loginState == AskApi::LoginState::kLoginSuccess) { + Q_EMIT loginSuccessed(); + // switch to ask page. + } +} + +CodeGeeXManager::CodeGeeXManager(QObject *parent) + : QObject(parent) +{ + initConnections(); + loadConfig(); +} + +void CodeGeeXManager::initConnections() +{ + connect(&askApi, &AskApi::response, this, &CodeGeeXManager::onResponse); + connect(&askApi, &AskApi::loginState, this, &CodeGeeXManager::recevieLoginState); +} + +void CodeGeeXManager::queryLoginState() +{ + if (!queryTimer) { + queryTimer = new QTimer(this); + queryTimer->setSingleShot(true); + + connect(queryTimer, &QTimer::timeout, this, [ = ] { + if (!sessionId.isEmpty()) + askApi.sendQueryRequest(sessionId); + }); + } + + queryTimer->start(1000); +} + +QString CodeGeeXManager::configFilePath() const +{ + return CustomPaths::user(CustomPaths::Configures) + "/codegeexcfg.json"; +} + +QString CodeGeeXManager::uuid() +{ + QUuid uuid = QUuid::createUuid(); + return uuid.toString().replace("{", "").replace("}", "").replace("-", "");; +} diff --git a/src/plugins/codegeex/codegeexmanager.h b/src/plugins/codegeex/codegeexmanager.h new file mode 100644 index 000000000..26d1428f2 --- /dev/null +++ b/src/plugins/codegeex/codegeexmanager.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef CODEGEEXMANAGER_H +#define CODEGEEXMANAGER_H + +#include "codegeex/askapi.h" +#include "data/messagedata.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + +class CodeGeeXManager : public QObject +{ + Q_OBJECT +public: + static CodeGeeXManager *instance(); + + void login(); + + void saveConfig(const QString &sessionId, const QString &userId); + void loadConfig(); + + void sendMessage(const QString &prompt); + void queryLoginState(); + +Q_SIGNALS: + void loginSuccessed(); + void requestMessageUpdate(const MessageData &msg); + +public Q_SLOTS: + void onResponse(const QString &msgID, const QString &data, const QString &event); + void recevieLoginState(CodeGeeX::AskApi::LoginState loginState); + +private: + explicit CodeGeeXManager(QObject *parent = nullptr); + + void initConnections(); + + QString configFilePath() const; + QString uuid(); + + CodeGeeX::AskApi askApi; + QString sessionId; + QString userId; + + QMap curSessionMsg {}; + + QTimer *queryTimer; +}; + +#endif // CODEGEEXMANAGER_H diff --git a/src/plugins/codegeex/data/messagedata.cpp b/src/plugins/codegeex/data/messagedata.cpp new file mode 100644 index 000000000..ee7926e6f --- /dev/null +++ b/src/plugins/codegeex/data/messagedata.cpp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "messagedata.h" + +#include + +MessageData::MessageData() +{ +} + +MessageData::MessageData(const QString &id, Type type) + : msgId(id), + msgType(type) +{ +} + +void MessageData::updateData(const QString &data) +{ + QStringList lines = data.split("\n"); + if (lines.last().isEmpty()) + lines.removeLast(); + + if (lines.length() < msgDataLines.length()) + return; + +// QStringList newLines = lines.mid(msgDataLines.length()); +// for (auto line : newLines) { +// if (line.startsWith("```")) { +// CodeData data; +// data.langueage = line.mid(3); +// codeDataList.append(data); +// } +// } + + msgData = data; + msgDataLines = lines; +// qInfo() << "update msg line" << msgDataLines; +} + +QString MessageData::messageID() const +{ + return msgId; +} + +MessageData::Type MessageData::messageType() const +{ + return msgType; +} + +QString MessageData::messageData() const +{ + return msgData; +} + +QStringList MessageData::messageLines() const +{ + return msgDataLines; +} diff --git a/src/plugins/codegeex/data/messagedata.h b/src/plugins/codegeex/data/messagedata.h new file mode 100644 index 000000000..e0238d77e --- /dev/null +++ b/src/plugins/codegeex/data/messagedata.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef MESSAGEDATA_H +#define MESSAGEDATA_H + +#include +#include + +struct CodeData { + QString langueage {}; + QString code {}; + QStringList codeLines {}; +}; + +class MessageData +{ +public: + enum Type{ + Ask, + Anwser + }; + explicit MessageData(); + explicit MessageData(const QString &id, Type type); + + void updateData(const QString &data); + + QString messageID() const; + Type messageType() const; + QString messageData() const; + QStringList messageLines() const; + +private: + QString msgId; + Type msgType; + QString msgData; + QList codeDataList {}; + QStringList msgDataLines {}; +}; + +#endif // MESSAGEDATA_H diff --git a/src/plugins/codegeex/widgets/askpagewidget.cpp b/src/plugins/codegeex/widgets/askpagewidget.cpp new file mode 100644 index 000000000..ed2c9c8a9 --- /dev/null +++ b/src/plugins/codegeex/widgets/askpagewidget.cpp @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "askpagewidget.h" +#include "intropage.h" +#include "messagecomponent.h" +#include "codegeexmanager.h" + +#include +#include +#include +#include +#include +#include +#include + +AskPageWidget::AskPageWidget(QWidget *parent) + : QWidget (parent) +{ + initUI(); + initConnection(); +} + +void AskPageWidget::setIntroPage() +{ + cleanWidgets(); + scrollArea->setWidget(new IntroPage(scrollArea)); + curState = Intro; +} + +void AskPageWidget::onMessageUpdate(const MessageData &msgData) +{ + if (curState == Intro) { + setSessionPage(); + curState = Session; + } + + if (!msgComponents.contains(msgData.messageID())) { + msgComponents.insert(msgData.messageID(), new MessageComponent(msgData)); + qobject_cast(messageContainer->layout())->insertWidget(msgComponents.count() - 1,msgComponents.value(msgData.messageID())); + msgComponents.value(msgData.messageID())->updateMessage(msgData); + } else { + msgComponents.value(msgData.messageID())->updateMessage(msgData); + } + + if (scrollArea->verticalScrollBar()->isVisible()) { + int maxValue = scrollArea->verticalScrollBar()->maximum(); + scrollArea->verticalScrollBar()->setValue(maxValue); + } +} + +void AskPageWidget::onSendBtnClicked() +{ + if (inputEdit) { + auto prompt = inputEdit->text(); + if (prompt.isEmpty()) + return; + + CodeGeeXManager::instance()->sendMessage(prompt); + inputEdit->clear(); + } +} + +void AskPageWidget::initUI() +{ + setContentsMargins(0, 0, 0, 0); + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + scrollArea = new QScrollArea(this); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setWidgetResizable(true); + scrollArea->setAlignment(Qt::AlignHCenter); + layout->addWidget(scrollArea); + + inputWidget = new QWidget(this); + layout->addWidget(inputWidget); + + initInputWidget(); +} + +void AskPageWidget::initInputWidget() +{ + QVBoxLayout *layout = new QVBoxLayout; + inputWidget->setLayout(layout); + + QHBoxLayout *editLayout = new QHBoxLayout; + layout->addLayout(editLayout); + + inputEdit = new QLineEdit(inputWidget); + inputEdit->setFixedHeight(50); + inputEdit->setPlaceholderText(tr("Enter a technical question here...")); + editLayout->addWidget(inputEdit); + + sendBtn = new QPushButton(inputWidget); + sendBtn->setText(tr("Send")); + editLayout->addWidget(sendBtn); + + QLabel *inputTips = new QLabel(inputWidget); + inputTips->setText(tr("Ctrl + Enter for Newline | \" / \" for command")); + inputTips->setIndent(10); + layout->addWidget(inputTips); +} + +void AskPageWidget::initConnection() +{ + connect(CodeGeeXManager::instance(), &CodeGeeXManager::requestMessageUpdate, this, &AskPageWidget::onMessageUpdate); + connect(sendBtn, &QPushButton::clicked, this, &AskPageWidget::onSendBtnClicked); + connect(inputEdit, &QLineEdit::returnPressed, this, &AskPageWidget::onSendBtnClicked); +} + +void AskPageWidget::cleanWidgets() +{ + if (auto currentWidget = scrollArea->takeWidget()) + currentWidget->deleteLater(); +} + +void AskPageWidget::setSessionPage() +{ + cleanWidgets(); + messageContainer = new QWidget(scrollArea); + scrollArea->setWidget(messageContainer); + + QVBoxLayout *layout = new QVBoxLayout(messageContainer); + messageContainer->setLayout(layout); + + layout->addStretch(1); +} diff --git a/src/plugins/codegeex/widgets/askpagewidget.h b/src/plugins/codegeex/widgets/askpagewidget.h new file mode 100644 index 000000000..021c1d0f4 --- /dev/null +++ b/src/plugins/codegeex/widgets/askpagewidget.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ASKPAGEWIDGET_H +#define ASKPAGEWIDGET_H + +#include "data/messagedata.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QScrollArea; +class QPushButton; +class QLineEdit; +QT_END_NAMESPACE + +class MessageComponent; +class AskPageWidget : public QWidget +{ + Q_OBJECT +public: + enum PageState : uint8_t { + Intro, + Session + }; + + explicit AskPageWidget(QWidget *parent = nullptr); + void setIntroPage(); + +Q_SIGNALS: + void sendPromot(const QString &promot); + +public Q_SLOTS: + void onMessageUpdate(const MessageData &msgData); + void onSendBtnClicked(); + +private: + void initUI(); + void initInputWidget(); + void initConnection(); + + void cleanWidgets(); + void setSessionPage(); + + PageState curState; + QScrollArea *scrollArea { nullptr }; + QWidget *inputWidget { nullptr }; + QWidget *messageContainer { nullptr }; + QPushButton *sendBtn { nullptr }; + QLineEdit *inputEdit { nullptr }; + + QMap msgComponents {}; +}; + +#endif // ASKPAGEWIDGET_H diff --git a/src/plugins/codegeex/widgets/codeeditcomponent.cpp b/src/plugins/codegeex/widgets/codeeditcomponent.cpp new file mode 100644 index 000000000..e9b0ae3de --- /dev/null +++ b/src/plugins/codegeex/widgets/codeeditcomponent.cpp @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "codeeditcomponent.h" + +#include +#include +#include +#include +#include +#include + +CodeEditComponent::CodeEditComponent(QWidget *parent) + : QWidget (parent) +{ + initUI(); +} + +void CodeEditComponent::showButtons(CodeEditComponent::ShowButtonsType type) +{ + titleWidget->setVisible(true); + switch (type) { + case InsertOnly: + insertButton->setVisible(true); + copyButton->setVisible(false); + break; + case CopyOnly: + insertButton->setVisible(false); + copyButton->setVisible(true); + break; + case CopyAndInsert: + insertButton->setVisible(true); + copyButton->setVisible(true); + break; + default: + insertButton->setVisible(false); + copyButton->setVisible(false); + break; + } +} + +void CodeEditComponent::setTitle(const QString &title) +{ + titleWidget->setVisible(true); + this->title->setText(title); +} + +void CodeEditComponent::setReadOnly(bool readOnly) +{ + if (codeEdit) + codeEdit->setReadOnly(readOnly); +} + +void CodeEditComponent::setUpdateHeight(bool update) +{ + heightUpdate = update; +} + +void CodeEditComponent::updateCode(const QString &code) +{ + if (codeEdit) { + codeEdit->setPlainText(code); + if (heightUpdate) { + QTextDocument *doc = codeEdit->document(); + qreal height = doc->size().height() * codeEdit->fontMetrics().height(); + height += 15; + codeEdit->setFixedHeight(static_cast(height)); + } + } +} + +void CodeEditComponent::updateCode(const QStringList &codeLines) +{ + QString code {}; + for (auto line : codeLines) { + code += line; + code += "\n"; + } + code.chop(1); + updateCode(code); +} + +void CodeEditComponent::cleanFinalLine() +{ + QString text = codeEdit->toPlainText(); + if (text.endsWith("``")) + text.chop(3); + if (text.endsWith("`")) + text.chop(2); + updateCode(text); +} + +void CodeEditComponent::initUI() +{ + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + titleWidget = new QWidget(this); + layout->addWidget(titleWidget); + initTitleWidgets(); + titleWidget->setVisible(false); + + codeEdit = new QPlainTextEdit(this); + codeEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); + layout->addWidget(codeEdit); +} + +void CodeEditComponent::initTitleWidgets() +{ + QHBoxLayout *layout = new QHBoxLayout; + layout->setDirection(QHBoxLayout::RightToLeft); + titleWidget->setLayout(layout); + + copyButton = new QPushButton(titleWidget); + copyButton->setText(tr("Copy")); + layout->addWidget(copyButton); + + insertButton = new QPushButton(titleWidget); + insertButton->setText(tr("Insert")); + layout->addWidget(insertButton); + + layout->addStretch(1); + + title = new QLabel(titleWidget); + title->setText(""); + layout->addWidget(title); +} diff --git a/src/plugins/codegeex/widgets/codeeditcomponent.h b/src/plugins/codegeex/widgets/codeeditcomponent.h new file mode 100644 index 000000000..370bf79b4 --- /dev/null +++ b/src/plugins/codegeex/widgets/codeeditcomponent.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef CODEEDITCOMPONENT_H +#define CODEEDITCOMPONENT_H + + +#include + +QT_BEGIN_NAMESPACE +class QPushButton; +class QPlainTextEdit; +class QLabel; +QT_END_NAMESPACE + +class CodeEditComponent : public QWidget +{ + Q_OBJECT +public: + enum ShowButtonsType { + None, + CopyOnly, + InsertOnly, + CopyAndInsert + }; + + explicit CodeEditComponent(QWidget *parent = nullptr); + void showButtons(ShowButtonsType type); + void setTitle(const QString &title); + void setReadOnly(bool readOnly); + + void setUpdateHeight(bool update); + void updateCode(const QString &code); + void updateCode(const QStringList &codeLines); + + void cleanFinalLine(); + +private: + void initUI(); + void initTitleWidgets(); + +// void updateEditHeight(int rowCount); + + QWidget *titleWidget { nullptr }; + QPushButton *copyButton { nullptr }; + QPushButton *insertButton { nullptr }; + QPlainTextEdit *codeEdit { nullptr }; + QLabel *title { nullptr }; + + bool heightUpdate { false }; +}; + +#endif // CODEEDITCOMPONENT_H diff --git a/src/plugins/codegeex/widgets/codegeexwidget.cpp b/src/plugins/codegeex/widgets/codegeexwidget.cpp new file mode 100644 index 000000000..5283fb6b9 --- /dev/null +++ b/src/plugins/codegeex/widgets/codegeexwidget.cpp @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "codegeexwidget.h" +#include "askpagewidget.h" +#include "translationpagewidget.h" +#include "codegeexmanager.h" + +#include +#include +#include +#include +#include + +CodeGeeXWidget::CodeGeeXWidget(QWidget *parent) + : QWidget(parent) +{ + initUI(); + initLoginConnection(); + CodeGeeXManager::instance()->queryLoginState(); +} + +void CodeGeeXWidget::onLoginSuccessed() +{ + if (loginBtn) { + auto mainLayout = qobject_cast(layout()); + mainLayout->removeWidget(loginBtn); + delete loginBtn; + loginBtn = nullptr; + } + + initAskWidget(); +} + +void CodeGeeXWidget::initUI() +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + QVBoxLayout *mainLayout = new QVBoxLayout; + setLayout(mainLayout); + + loginBtn = new QPushButton(this); + loginBtn->setText(tr("Login")); + + connect(loginBtn, &QPushButton::clicked, this, [ = ]{ + qInfo() << "on login clicked"; + CodeGeeXManager::instance()->login(); + }); +} + +void CodeGeeXWidget::initLoginConnection() +{ + connect(CodeGeeXManager::instance(), &CodeGeeXManager::loginSuccessed, this, &CodeGeeXWidget::onLoginSuccessed); +} + +void CodeGeeXWidget::initAskWidget() +{ + tabBar = new QTabBar(this); + tabBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + tabBar->setContentsMargins(0, 0, 0, 0); + stackWidget = new QStackedWidget(this); + stackWidget->setContentsMargins(0, 0, 0, 0); + stackWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + auto mainLayout = qobject_cast(layout()); + mainLayout->setSpacing(0); + mainLayout->addWidget(tabBar, 0); + mainLayout->addWidget(stackWidget, 1); + + initTabBar(); + initStackWidget(); + initAskWidgetConnection(); +} + +void CodeGeeXWidget::initTabBar() +{ + tabBar->setShape(QTabBar::TriangularNorth); + tabBar->addTab("Ask CodeGeeX"); + tabBar->addTab("Translation"); +} + +void CodeGeeXWidget::initStackWidget() +{ + AskPageWidget *askPage = new AskPageWidget; + TranslationPageWidget *transPage = new TranslationPageWidget; + + stackWidget->insertWidget(0, askPage); + stackWidget->insertWidget(1, transPage); + stackWidget->setCurrentIndex(0); + + askPage->setIntroPage(); +} + +void CodeGeeXWidget::initAskWidgetConnection() +{ + connect(tabBar, &QTabBar::currentChanged, stackWidget, &QStackedWidget::setCurrentIndex); +} diff --git a/src/plugins/codegeex/widgets/codegeexwidget.h b/src/plugins/codegeex/widgets/codegeexwidget.h new file mode 100644 index 000000000..c43bb9bee --- /dev/null +++ b/src/plugins/codegeex/widgets/codegeexwidget.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef CODEGEEXWIDGET_H +#define CODEGEEXWIDGET_H + +#include + +QT_BEGIN_NAMESPACE +class QTabBar; +class QStackedWidget; +class QPushButton; +QT_END_NAMESPACE + +class CodeGeeXWidget : public QWidget +{ + Q_OBJECT +public: + explicit CodeGeeXWidget(QWidget* parent = nullptr); + +public Q_SLOTS: + void onLoginSuccessed(); + +private: + void initUI(); + void initLoginConnection(); + + void initAskWidget(); + void initTabBar(); + void initStackWidget(); + void initAskWidgetConnection(); + + QPushButton *loginBtn { nullptr }; + QTabBar *tabBar { nullptr }; + QStackedWidget *stackWidget { nullptr }; +}; + +#endif // CODEGEEXWIDGET_H diff --git a/src/plugins/codegeex/widgets/intropage.cpp b/src/plugins/codegeex/widgets/intropage.cpp new file mode 100644 index 000000000..bc80ca3c2 --- /dev/null +++ b/src/plugins/codegeex/widgets/intropage.cpp @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "intropage.h" +#include "codegeexmanager.h" + +#include +#include +#include +#include +#include +#include +#include + +IntroPage::IntroPage(QWidget *parent) + : QWidget (parent) +{ + initUI(); +} + +void IntroPage::initUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout; + setLayout(mainLayout); + + mainLayout->addSpacing(50); + initLogo(); + mainLayout->addSpacing(50); + initIntroContent(); + + mainLayout->addStretch(1); + initSuggestContent(); +} + +void IntroPage::initLogo() +{ + QVBoxLayout *logoLayout = new QVBoxLayout; + logoLayout->setAlignment(Qt::AlignCenter); + qobject_cast(layout())->addLayout(logoLayout); + + QLabel *logoLabel = new QLabel(this); + logoLabel->setAlignment(Qt::AlignHCenter); + logoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + auto font = logoLabel->font(); + font.setPointSize(14); + logoLabel->setFont(font); + logoLabel->setText(tr("Welcome to CodeGeeX")); + logoLayout->addWidget(logoLabel); +} + +void IntroPage::initIntroContent() +{ + QVBoxLayout *introLayout = new QVBoxLayout; + introLayout->setAlignment(Qt::AlignCenter); + qobject_cast(layout())->addLayout(introLayout); + + appendDescLabel(introLayout, tr("CodeGeeX provides code completion suggestions in editor, Press Tab to accept.")); + appendDescLabel(introLayout, tr("Select code and right-click to add comments or translate code.")); + appendDescLabel(introLayout, tr("Also, you can directly ask CodeGeeX any questions.")); +} + +void IntroPage::initSuggestContent() +{ + QVBoxLayout *suggestLayout = new QVBoxLayout; + qobject_cast(layout())->addLayout(suggestLayout); + + QLabel *suggestLabel = new QLabel(this); + suggestLabel->setText(tr("Try the following questions:")); + suggestLayout->addWidget(suggestLabel); + + appendSuggestButton(suggestLayout, tr("How to iterate through a dictionary in Python?")); + appendSuggestButton(suggestLayout, tr("Write a quicksort function.")); + appendSuggestButton(suggestLayout, tr("What is FIFO?")); + appendSuggestButton(suggestLayout, tr("What is the best way to start learning JavaScript?")); +} + +void IntroPage::appendDescLabel(QVBoxLayout *layout, const QString &text) +{ + QLabel *descLabel = new QLabel(this); + descLabel->setAlignment(Qt::AlignHCenter); + descLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + descLabel->setWordWrap(true); + descLabel->setText(text); + layout->addWidget(descLabel); +} + +void IntroPage::appendSuggestButton(QVBoxLayout *layout, const QString &text) +{ + QPushButton *suggestButton = new QPushButton(this); + suggestButton->setFixedHeight(50); + QFont buttonFont = suggestButton->font(); + buttonFont.setPointSize(14); + suggestButton->setFont(buttonFont); + suggestButton->setText(text); + layout->addWidget(suggestButton); + + connect(suggestButton, &QPushButton::clicked, [ = ] { + CodeGeeXManager::instance()->sendMessage(suggestButton->text()); + Q_EMIT this->sendQuestion(suggestButton->text()); + }); +} diff --git a/src/plugins/codegeex/widgets/intropage.h b/src/plugins/codegeex/widgets/intropage.h new file mode 100644 index 000000000..2012cc17f --- /dev/null +++ b/src/plugins/codegeex/widgets/intropage.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef INTROPAGE_H +#define INTROPAGE_H + +#include + +QT_BEGIN_NAMESPACE +class QVBoxLayout; +QT_END_NAMESPACE + +class IntroPage : public QWidget +{ + Q_OBJECT +public: + explicit IntroPage(QWidget *parent = nullptr); + +Q_SIGNALS: + void sendQuestion(const QString &question); + +private: + void initUI(); + void initLogo(); + void initIntroContent(); + void initSuggestContent(); + + void appendDescLabel(QVBoxLayout *layout, const QString &text); + void appendSuggestButton(QVBoxLayout *layout, const QString &text); +}; + +#endif // INTROPAGE_H diff --git a/src/plugins/codegeex/widgets/messagecomponent.cpp b/src/plugins/codegeex/widgets/messagecomponent.cpp new file mode 100644 index 000000000..406c625e7 --- /dev/null +++ b/src/plugins/codegeex/widgets/messagecomponent.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "messagecomponent.h" +#include "codeeditcomponent.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MessageComponent::MessageComponent(const MessageData &msgData, QWidget *parent) + : QWidget(parent), + messageData(msgData) +{ + initUI(); +} +void MessageComponent::updateMessage(const MessageData &msgData) +{ + if (currentUpdateState == CodeEdit && msgData.messageLines().last() == "```") { + curUpdateEdit->cleanFinalLine(); + currentUpdateState = Label; + messageData = msgData; + return; + } else if (currentUpdateState == Label && msgData.messageLines().last().startsWith("```")) { + if (curUpdateLabel->text() == "``" | curUpdateLabel->text() == "`") { + msgLayout->removeWidget(curUpdateLabel); + delete curUpdateLabel; + curUpdateLabel = nullptr; + } + currentUpdateState = CodeEdit; + curUpdateEdit = new CodeEditComponent(this); + curUpdateEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); + curUpdateEdit->setReadOnly(true); + curUpdateEdit->setUpdateHeight(true); + curUpdateEdit->showButtons(CodeEditComponent::CopyAndInsert); + msgLayout->addWidget(curUpdateEdit); + } + + switch (currentUpdateState) { + case Label: + if (!curUpdateLabel) { + curUpdateLabel = new QLabel(this); + curUpdateLabel->setWordWrap(true); + msgLayout->addWidget(curUpdateLabel); + } else if (msgData.messageLines().length() > messageData.messageLines().length()) { + curUpdateLabel = new QLabel(this); + curUpdateLabel->setWordWrap(true); + msgLayout->addWidget(curUpdateLabel); + } + curUpdateLabel->setText(msgData.messageLines().last()); + break; + case CodeEdit: + if (msgData.messageLines().at(msgData.messageLines().length() - 2) == "```") { + curUpdateEdit->cleanFinalLine(); + currentUpdateState = Label; + updateMessage(msgData); + return; + } + if (curUpdateEdit) { + int startIndex = msgData.messageLines().lastIndexOf(QRegularExpression("```([a-z]+|[A-Z]+)")); + curUpdateEdit->updateCode(msgData.messageLines().mid(startIndex + 1)); + } + break; + } + messageData = msgData; +} + +void MessageComponent::initUI() +{ + QVBoxLayout *msgLayout = new QVBoxLayout; + setLayout(msgLayout); + +// if (messageData.messageType() == MessageData::Ask) { +// auto palatte = palette(); +// auto windowColor = palatte.color(QPalette::Normal, QPalette::Window); +// windowColor.setRgb(windowColor.red() + 10, windowColor.green() + 10, windowColor.blue() + 10); +// palatte.setColor(QPalette::Window, Qt::white); +// setPalette(palatte); +// setAutoFillBackground(true); +// } + + initSenderInfo(); + initMessageSection(); +} + +void MessageComponent::initSenderInfo() +{ + QHBoxLayout *senderInfoLayout = new QHBoxLayout; + qobject_cast(layout())->addLayout(senderInfoLayout); + + senderHead = new QLabel(this); + senderName = new QLabel(this); + + QImage *headImg = new QImage(QSize(40, 40), QImage::Format_ARGB32); + switch (messageData.messageType()) { + case MessageData::Ask: + headImg->fill(Qt::red); + senderName->setText("You"); + break; + case MessageData::Anwser: + headImg->fill(Qt::blue); + senderName->setText("CodeGeeX"); + break; + } + senderHead->setPixmap(QPixmap::fromImage(*headImg)); + + senderInfoLayout->addWidget(senderHead); + senderInfoLayout->addWidget(senderName); + senderInfoLayout->addStretch(1); +} + +void MessageComponent::initMessageSection() +{ + msgLayout = new QVBoxLayout; + qobject_cast(layout())->addLayout(msgLayout); +} diff --git a/src/plugins/codegeex/widgets/messagecomponent.h b/src/plugins/codegeex/widgets/messagecomponent.h new file mode 100644 index 000000000..84a21fdcc --- /dev/null +++ b/src/plugins/codegeex/widgets/messagecomponent.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef MESSAGECOMPONENT_H +#define MESSAGECOMPONENT_H + +#include "data/messagedata.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QImage; +class QLabel; +class QVBoxLayout; +QT_END_NAMESPACE + +class CodeEditComponent; +class MessageComponent : public QWidget +{ + enum UpdateState : uint8_t { + Label, + CodeEdit + }; +public: + explicit MessageComponent(const MessageData &msgData, QWidget *parent = nullptr); + void updateMessage(const MessageData &msgData); + +private: + void initUI(); + void initSenderInfo(); + void initMessageSection(); + + void createCurrentUpdateWidget(); + + QLabel *senderHead { nullptr }; + QLabel *senderName { nullptr }; + QLabel *context { nullptr }; + + QVBoxLayout *msgLayout { nullptr }; + + UpdateState currentUpdateState = Label; + CodeEditComponent *curUpdateEdit { nullptr }; + QLabel *curUpdateLabel { nullptr }; + + MessageData messageData; +}; + +#endif // MESSAGECOMPONENT_H diff --git a/src/plugins/codegeex/widgets/translationpagewidget.cpp b/src/plugins/codegeex/widgets/translationpagewidget.cpp new file mode 100644 index 000000000..8d99d9dcf --- /dev/null +++ b/src/plugins/codegeex/widgets/translationpagewidget.cpp @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "translationpagewidget.h" +#include "codeeditcomponent.h" + +#include +#include +#include +#include +#include + +TranslationPageWidget::TranslationPageWidget(QWidget *parent) + : QWidget (parent) +{ + initUI(); +} + +void TranslationPageWidget::initUI() +{ + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + CodeEditComponent *inputEdit = new CodeEditComponent(this); + inputEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + inputEdit->showButtons(CodeEditComponent::None); + inputEdit->setTitle("Input Code"); + inputEdit->setFixedHeight(200); + layout->addWidget(inputEdit); + + QHBoxLayout *midLayout = new QHBoxLayout; + + QVBoxLayout *comboBoxLayout = new QVBoxLayout; + QLabel *comboBoxLabel = new QLabel(this); + comboBoxLabel->setFixedHeight(20); + comboBoxLabel->setText(tr("Translate Into:")); + comboBoxLayout->addWidget(comboBoxLabel); + + layout->addSpacing(20); + + QComboBox *box = new QComboBox(this); + box->addItems({ "c++", "c", "python", "java", "javascript" }); + comboBoxLayout->addWidget(box); + + midLayout->addLayout(comboBoxLayout); + + midLayout->addSpacing(100); + + QPushButton *transButton = new QPushButton(this); + transButton->setText(tr("Translate")); + midLayout->addWidget(transButton); + + layout->addLayout(midLayout); + + layout->addSpacing(20); + + CodeEditComponent *outputEdit = new CodeEditComponent(this); + outputEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + outputEdit->setTitle(tr("Output Code")); + outputEdit->setFixedHeight(200); + outputEdit->showButtons(CodeEditComponent::InsertOnly); + outputEdit->setReadOnly(true); + layout->addWidget(outputEdit); + + layout->addStretch(1); +} diff --git a/src/plugins/codegeex/widgets/translationpagewidget.h b/src/plugins/codegeex/widgets/translationpagewidget.h new file mode 100644 index 000000000..616b0eaa7 --- /dev/null +++ b/src/plugins/codegeex/widgets/translationpagewidget.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef TRANSLATIONPAGEWIDGET_H +#define TRANSLATIONPAGEWIDGET_H + +#include + +class TranslationPageWidget : public QWidget +{ + Q_OBJECT +public: + explicit TranslationPageWidget(QWidget *parent = nullptr); + +private: + void initUI(); +}; + +#endif // TRANSLATIONPAGEWIDGET_H