main
zhuzihcu 2023-03-17 14:05:27 +08:00
parent 2de9d78f41
commit 9c121c6ba9
14 changed files with 347 additions and 17 deletions

View File

@ -65,6 +65,10 @@
# 部分效果预览 # 部分效果预览
## 内置一个ChatGPT聊天Demo
![](doc/preview/chatpgt.png)
## 各种Button按钮 ## 各种Button按钮
![](doc/preview/buttons.png) ![](doc/preview/buttons.png)

BIN
doc/preview/chatgpt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

View File

@ -15,6 +15,7 @@ Window {
"/":"qrc:/page/MainPage.qml", "/":"qrc:/page/MainPage.qml",
"/about":"qrc:/page/AboutPage.qml", "/about":"qrc:/page/AboutPage.qml",
"/login":"qrc:/page/LoginPage.qml", "/login":"qrc:/page/LoginPage.qml",
"/chat":"qrc:/page/ChatPage.qml",
} }
FluApp.initialRoute = "/" FluApp.initialRoute = "/"
FluApp.run() FluApp.run()

View File

@ -0,0 +1,41 @@
#include "ChatController.h"
ChatController::ChatController(QObject *parent)
: QObject{parent}
{
isLoading(false);
networkManager = new QNetworkAccessManager(this);
}
void ChatController::sendMessage(const QString& text){
isLoading(true);
QUrl apiUrl("https://api.openai.com/v1/engines/text-davinci-003/completions");
QNetworkRequest request(apiUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", "Bearer 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");
QJsonObject requestData;
requestData.insert("prompt", text);
requestData.insert("max_tokens", 1000);
requestData.insert("temperature", 0.5);
QJsonDocument requestDoc(requestData);
QByteArray requestDataBytes = requestDoc.toJson();
QNetworkReply* reply = networkManager->post(request, requestDataBytes);
connect(reply, &QNetworkReply::finished,this, [=]() {
QString responseString = QString::fromUtf8(reply->readAll());
qDebug() << responseString;
QJsonDocument doc = QJsonDocument::fromJson(responseString.toUtf8());
QJsonObject jsonObj = doc.object();
QString text = jsonObj.value("choices").toArray().at(0).toObject().value("text").toString();
if(text.isEmpty()){
text = "不好意思,我似乎听不懂您的意思";
}
responseData(text);
reply->deleteLater();
isLoading(false);
});
connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::errorOccurred), this, [=](QNetworkReply::NetworkError) {
qDebug() << "Network error occurred: " << reply->errorString();
reply->deleteLater();
isLoading(false);
});
}

27
example/ChatController.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef CHATCONTROLLER_H
#define CHATCONTROLLER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QByteArray>
#include "stdafx.h"
class ChatController : public QObject
{
Q_OBJECT
Q_PROPERTY_AUTO(bool,isLoading)
Q_PROPERTY_AUTO(QString,responseData);
public:
explicit ChatController(QObject *parent = nullptr);
Q_INVOKABLE void sendMessage(const QString& text);
private:
QNetworkAccessManager* networkManager;
};
#endif // CHATCONTROLLER_H

View File

@ -1,9 +1,10 @@
QT += quick concurrent QT += quick concurrent network
CONFIG += c++11 CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS QT_NO_WARNING_OUTPUT DEFINES += QT_DEPRECATED_WARNINGS QT_NO_WARNING_OUTPUT
SOURCES += \ SOURCES += \
ChatController.cpp \
main.cpp main.cpp
RESOURCES += qml.qrc RESOURCES += qml.qrc
@ -24,3 +25,6 @@ CONFIG(debug,debug|release) {
} else { } else {
DESTDIR = $$absolute_path($${_PRO_FILE_PWD_}/../bin/release) DESTDIR = $$absolute_path($${_PRO_FILE_PWD_}/../bin/release)
} }
HEADERS += \
ChatController.h

View File

@ -4,6 +4,7 @@
#include <QDir> #include <QDir>
#include <QQuickWindow> #include <QQuickWindow>
#include <QProcess> #include <QProcess>
#include "ChatController.h"
QMap<QString, QVariant> properties(){ QMap<QString, QVariant> properties(){
QMap<QString, QVariant> map; QMap<QString, QVariant> map;
@ -20,6 +21,9 @@ int main(int argc, char *argv[])
// QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); // QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
qmlRegisterType<ChatController>("Controller",1,0,"ChatController");
QMapIterator<QString, QVariant> iterator(properties()); QMapIterator<QString, QVariant> iterator(properties());
while (iterator.hasNext()) { while (iterator.hasNext()) {
iterator.next(); iterator.next();

View File

@ -34,7 +34,7 @@ FluWindow {
fontStyle: FluText.Title fontStyle: FluText.Title
} }
FluText{ FluText{
text:"v1.0.9" text:"v1.0.10"
fontStyle: FluText.Body fontStyle: FluText.Body
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
} }

230
example/page/ChatPage.qml Normal file
View File

@ -0,0 +1,230 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import FluentUI 1.0
import Controller 1.0
FluWindow {
width: 680
height: 600
minimumWidth: 500
minimumHeight: 600
title:"ChatGPT"
ChatController{
id:controller
onResponseDataChanged: {
appendMessage(false,responseData)
}
}
ListModel{
id:model_message
ListElement{
isMy:false
text:"欢迎使用ChatGPT"
}
ListElement{
isMy:true
text:"好的3Q"
}
}
FluAppBar{
id:appbar
title:"ChatGPT"
}
Component{
id:com_text
TextEdit {
text: modelData.text
wrapMode: Text.WrapAnywhere
readOnly: true
textFormat: Text.RichText
selectByMouse: true
selectByKeyboard: true
selectedTextColor: color
color:FluColors.Black
selectionColor: {
if(FluTheme.isDark){
return FluTheme.primaryColor.lighter
}else{
return FluTheme.primaryColor.dark
}
}
width: Math.min(list_message.width-200,600,implicitWidth)
}
}
FluArea{
id:layout_content
anchors{
top: appbar.bottom
left: parent.left
right: parent.right
bottom: layout_bottom.top
margins: 10
}
color: FluTheme.isDark ? Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(245/255,245/255,245/255,1)
ListView{
id:list_message
anchors.fill: parent
model:model_message
clip: true
ScrollBar.vertical: FluScrollBar {}
preferredHighlightBegin: 0
preferredHighlightEnd: 0
highlightMoveDuration: 0
header:Item{
width: list_message.width
height:20
}
footer:Item{
width: list_message.width
height:20
}
delegate: Item{
width: ListView.view.width
height: childrenRect.height
FluRectangle{
id:item_avatar
width: 30
height: 30
radius:[15,15,15,15]
anchors{
right: isMy ? parent.right : undefined
rightMargin: isMy ? 20 : undefined
left: isMy ? undefined : parent.left
leftMargin: isMy ? undefined : 20
top:parent.top
}
Image {
asynchronous: true
anchors.fill: parent
sourceSize: Qt.size(100,100)
source: isMy ? "qrc:/res/svg/avatar_2.svg" : "qrc:/res/image/logo_openai.png"
}
}
Rectangle{
id:item_layout_content
color: isMy ? "#FF95EC69" : "#FFFFFF"
width: item_msg_loader.width+10
height: item_msg_loader.height+10
radius: 3
anchors{
top: item_avatar.top
right: isMy ? item_avatar.left : undefined
rightMargin: isMy ? 10 : undefined
left: isMy ? undefined : item_avatar.right
leftMargin: isMy ? undefined : 10
}
Loader{
id:item_msg_loader
property var modelData: model
anchors.centerIn: parent
sourceComponent: com_text
}
}
Item{
id:item_layout_bottom
width: parent.width
anchors.top: item_layout_content.bottom
height: 20
}
}
}
}
FluArea{
id:layout_bottom
height: 90
anchors{
bottom: parent.bottom
bottomMargin: 10
left: parent.left
right: parent.right
leftMargin: 10
rightMargin: 10
}
ScrollView{
anchors{
bottom: parent.bottom
left: parent.left
right: button_send.left
bottomMargin: 10
leftMargin: 10
rightMargin: 10
}
height: Math.min(textbox.implicitHeight,64)
FluMultiLineTextBox{
id:textbox
}
}
FluFilledButton{
id:button_send
text:controller.isLoading ? timer_loading.loadingText :"发送"
anchors{
bottom: parent.bottom
right: parent.right
bottomMargin: 10
rightMargin: 10
}
width: 60
disabled: controller.isLoading
onClicked:{
var text = textbox.text
appendMessage(true,text)
controller.sendMessage(text)
textbox.clear()
}
Timer{
id:timer_loading
property int count : 0
property string loadingText : ""
interval: 500
running: controller.isLoading
repeat: true
onTriggered: {
switch(count%3){
case 0:
loadingText = "."
break
case 1:
loadingText = ".."
break
case 2:
loadingText = "..."
break
default:
loadingText = ""
break
}
count++
}
}
}
}
function appendMessage(isMy,text){
model_message.append({isMy:isMy,text:text})
list_message.positionViewAtEnd()
}
}

View File

@ -204,6 +204,35 @@ FluWindow {
items:original_items items:original_items
footerItems:footer_items footerItems:footer_items
actions:[
Image {
width: 30
height: 30
Layout.preferredWidth: 30
Layout.preferredHeight: 30
sourceSize: Qt.size(60,60)
source: "qrc:/res/image/logo_openai.png"
Layout.rightMargin: 5
MouseArea{
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
FluApp.navigate("/chat")
}
}
},
FluText{
text:"夜间模式"
fontStyle: FluText.Body
},
FluToggleSwitch{
selected: FluTheme.isDark
clickFunc:function(){
FluTheme.isDark = !FluTheme.isDark
}
}
]
Component.onCompleted: { Component.onCompleted: {
nav_view.setCurrentIndex(1) nav_view.setCurrentIndex(1)
nav_view.push("qrc:/T_Buttons.qml") nav_view.push("qrc:/T_Buttons.qml")

View File

@ -38,5 +38,7 @@
<file>res/image/banner_1.jpg</file> <file>res/image/banner_1.jpg</file>
<file>res/image/banner_2.jpg</file> <file>res/image/banner_2.jpg</file>
<file>res/image/banner_3.jpg</file> <file>res/image/banner_3.jpg</file>
<file>res/image/logo_openai.png</file>
<file>page/ChatPage.qml</file>
</qresource> </qresource>
</RCC> </RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -16,6 +16,8 @@ Item {
property bool displaMinimalNav : false property bool displaMinimalNav : false
property alias actions: layout_actions.data
onDisplayModeChanged: { onDisplayModeChanged: {
if(displayMode === FluNavigationView.Minimal){ if(displayMode === FluNavigationView.Minimal){
anim_navi.enabled = false anim_navi.enabled = false
@ -172,22 +174,13 @@ Item {
RowLayout{ RowLayout{
id:layout_actions
anchors{ anchors{
right: parent.right right: parent.right
rightMargin: 14 rightMargin: 14
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
spacing: 5 spacing: 5
FluText{
text:"夜间模式"
fontStyle: FluText.Body
}
FluToggleSwitch{
selected: FluTheme.isDark
clickFunc:function(){
FluTheme.isDark = !FluTheme.isDark
}
}
} }
} }
@ -377,11 +370,8 @@ Item {
} }
} }
} }
} }
function push(url){ function push(url){
nav_swipe.push(url) nav_swipe.push(url)
} }

View File

@ -6,8 +6,6 @@ Item{
id:root id:root
property var radius:[0,0,0,0] property var radius:[0,0,0,0]
property color color : "#FFFFFF" property color color : "#FFFFFF"
property color borderColor:"red"
property int borderWidth: 1
property bool shadow: true property bool shadow: true
default property alias contentItem: container.data default property alias contentItem: container.data