Skip to content

Commit

Permalink
Merge pull request #84 from MisterGC/better-webaccess
Browse files Browse the repository at this point in the history
Wrapper for ClayWebAccess which allows declaring HTTP endpoints and generates a client JS object using the declaration (incl. dedicated Sandbox).
  • Loading branch information
MisterGC authored Jul 2, 2023
2 parents 0f5b055 + 10f1418 commit 6af2196
Show file tree
Hide file tree
Showing 6 changed files with 418 additions and 19 deletions.
2 changes: 1 addition & 1 deletion plugins/clay_network/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ clay_p( Network
SOURCES
claynetworknode.cpp claywebaccess.cpp connection.cpp peermanager.cpp server.cpp
QML_FILES
ClayNetworkUser.qml Sandbox.qml
ClayNetworkUser.qml ClayHttpClient.qml Sandbox.qml SandboxHttpClient.qml
LINK_LIBS
Qt::Core Qt::Quick Qt::Qml Qt::Network
)
96 changes: 96 additions & 0 deletions plugins/clay_network/ClayHttpClient.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// (c) Clayground Contributors - MIT License, see "LICENSE" file

import QtQuick
import Clayground.Network

// Component that generates an easy to use API based on provided
// endpoint and authorization configuration.
Item {
id: _clayHttpClient

// END POINT CONFIGURATION

// There is only one base URL per client
// this allows keeping the (relative) URLs in the endpoints short
// Example: https://acme.com/products
property string baseUrl: ""

// Endpoints, each entry with the following structure:
// {HTTP_METHOD} {urlWithPathAndQueryParams} [{nameOfJsonBody}]
// Example: GET flyingObjects/{type} {aerodynamicRequirements}
// This will generate a method with client.api.flyingObjects("ufo", "{friction: low}"}
property var endpoints: ({})

// Object which contains all the methods based on the baseUrl and
// end point configuration.
property var api: ({})


// AUTHENTICATION/AUTHORIZATION
// TODO Add support for basic auth and API keys

// When using Bearer Authentication
// Syntax: {token}
property string bearerToken: ""


// RESULT REPORTING

// method has been executed successfully
signal reply(int requestId, int returnCode, string text);

// an error happened during execution
signal error(int requestId, int returnCode, string text);


onBaseUrlChanged: _updateServiceAccess()
onEndpointsChanged: _updateServiceAccess()

ClayWebAccess {
id: _webAccess
onReply: (reqId, returnCode, result) => {
_clayHttpClient.reply(reqId, returnCode, result)
}
onError: (reqId, returnCode, result) => {
_clayHttpClient.error(reqId, returnCode, result)
}
}

function _updateServiceAccess() {
_clayHttpClient.api = {};

var authString = "";
if (_clayHttpClient.bearerToken !== "") {
authString = "Bearer " + _clayHttpClient.bearerToken;
}

for (var endpoint in _clayHttpClient.endpoints) {
var parts = _clayHttpClient.endpoints[endpoint].split(' ');
var httpMethod = parts[0].toUpperCase();
var endpointUrl = parts[1];
var jsonName = parts.length > 2 ? parts[2] : "";

// Ensure that every function has its relevant argument
// values otherwise all would just reference the last
// values of endpointUrl, httpMethod and jsonName
(function(endpointUrl, httpMethod, jsonName) {
_clayHttpClient.api[endpoint] = function() {
var url = _clayHttpClient.baseUrl + "/" + endpointUrl;
var args = Array.prototype.slice.call(arguments);
url = url.replace(/\{.*?\}/g, function() {
return args.shift();
});
switch (httpMethod) {
case "GET":
return _webAccess.get(url, authString);
case "POST":
var json = jsonName !== "" && args.length ? args[args.length - 1] : "";
if (typeof json == "object" && json !== null && !Array.isArray(json))
json = JSON.stringify(json);
return _webAccess.post(url, json, authString);
}
}
})(endpointUrl, httpMethod, jsonName);
}
}
}
128 changes: 128 additions & 0 deletions plugins/clay_network/SandboxHttpClient.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// (c) Clayground Contributors - MIT License, see "LICENSE" file

import QtQuick
import QtQuick.Controls
import Clayground.Network

Rectangle {
anchors.fill: parent
color: "black"

ClayHttpClient
{
id: openAi

baseUrl: "https://api.openai.com"
endpoints: ({
complete: "POST v1/chat/completions {chat}"
})
// TODO: Change path to a file which contains the API key
// or put env.{my-variable-name} if it should be read from a
// env variable
bearerToken: "file:///path/to/bearer_token.txt"

onReply: (requestId, returnCode, text) => {
const reply = JSON.parse(text);
text = reply.choices[0].message.content;
messageModel.append({"source": "ChatAi", "message": text});
}
onError: (requestId, returnCode, text) => {txt.text = text; }

function complete(message)
{
let client = openAi.api;
const requestObj = {
model: "gpt-3.5-turbo",
messages: [{
role: "user",
content: message
}]
};
let requestId = client.complete(requestObj);
}

Component.onCompleted: {
// TODO: Activate if you want to see the chat API
// in action, don't forget to reference a valid
// Bearer token.
// complete("What is the meaning of life?");
}
}

ClayHttpClient
{
id: jsonPlaceholder

baseUrl: "https://jsonplaceholder.typicode.com"
endpoints: ({
pubPost: "POST posts {data}",
getPost: "GET posts/{postId}"
})

onReply: (requestId, returnCode, text) => {txt.text = text; }
onError: (requestId, returnCode, text) => {txt.text = text; }

function demo() {
let client = jsonPlaceholder.api;
let requestId = client.pubPost(JSON.stringify({"hohoh": "world"}));
//Hint: There are already 100 posts, 101 is the newly added one
requestId = client.getPost(101);
}

Component.onCompleted: {
// TODO: Uncomment to see the placeholder api in action
// demo()
}
}

ListView {
id: messageList
anchors.fill: parent
anchors.margins: 10
anchors.bottomMargin: 50

model: ListModel {
id: messageModel
}

delegate: Rectangle {
width: messageList.width
height: messageText.implicitHeight + 10
color: index % 2 === 0 ? "#303030" : "#4d4d4d"

Text {
id: messageText
color: "white"
width: parent.width - 10
anchors.centerIn: parent
text: model.source + ": " + model.message
wrapMode: Text.Wrap
}
}
}

TextField {
id: messageField
anchors.bottom: parent.bottom
width: parent.width
placeholderText: "Enter message"
color: "white"

onAccepted: {
messageModel.append({"source": "You", "message": text});
openAi.complete(text);
text = "";
messageList.positionViewAtEnd();
}
}

Text {
id: txt
width: parent.width * .75
wrapMode: Text.WordWrap
anchors.centerIn: parent
color: "white"
font.family: "Monospace"
}

}
1 change: 1 addition & 0 deletions plugins/clay_network/claynetworknode.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// (c) Clayground Contributors - MIT License, see "LICENSE" file
#include <QtNetwork>

#include "claynetworknode.h"
Expand Down
Loading

0 comments on commit 6af2196

Please sign in to comment.