Skip to content
Snippets Groups Projects
Commit f2071337 authored by Jari Helaakoski's avatar Jari Helaakoski
Browse files

QtWS2025 release

parent 8e941219
No related branches found
No related tags found
1 merge request!1QtWS2025 release
Showing
with 266 additions and 212 deletions
cmake_minimum_required(VERSION 3.21.1) cmake_minimum_required(VERSION 3.21.1)
add_subdirectory(aimodel)
project(QtAiInferenceApi LANGUAGES CXX) project(QtAiInferenceApi LANGUAGES CXX)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml)
set(QML_IMPORT_PATH ${QT_QML_OUTPUT_DIRECTORY}
CACHE STRING "Import paths for Qt Creator's code model"
FORCE
)
find_package(Qt6 6.8 REQUIRED COMPONENTS Core Gui Qml Quick Multimedia)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(${CMAKE_PROJECT_NAME} add_subdirectory(aimodel)
main.cpp add_subdirectory(tests)
)
qt_add_qml_module(${CMAKE_PROJECT_NAME}
URI qtaiinferenceapi
VERSION 1.0
RESOURCES
qtquickcontrols2.conf
QML_FILES
App.qml
Screen01.ui.qml
)
target_link_libraries(${CMAKE_PROJECT_NAME}
PRIVATE
Qt6::Quick
Qt6::Multimedia
QtAiModelApi
)
include(GNUInstallDirs)
install(TARGETS ${CMAKE_PROJECT_NAME}
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
...@@ -2,17 +2,22 @@ ...@@ -2,17 +2,22 @@
This projects contains the proof-of-concept for a new Qt AI Inference API. The purpose of the API is to let you easily use different types of AI models for inference from your Qt code, either from C++ or directly from QML! The API abstracts the details of the underlying model and framework implementations, allowing you to just tell what type of input and output you would like to use, and Qt will set things up for you! You can also chain different models together for pipelines. This projects contains the proof-of-concept for a new Qt AI Inference API. The purpose of the API is to let you easily use different types of AI models for inference from your Qt code, either from C++ or directly from QML! The API abstracts the details of the underlying model and framework implementations, allowing you to just tell what type of input and output you would like to use, and Qt will set things up for you! You can also chain different models together for pipelines.
**Disclaimer**
This API is in proof-of-concept stage and under active development, and not yet a part of the Qt framework. Hence, Qt's compatibility promise does not apply; the API can still change in breaking ways. But, it is also a great time to impact the direction it will take! For suggestions feel free to create a ticket in the Qt project's [JIRA](https://bugreports.qt.io), please use the label "QtAiApi" so we can easily find them and collect them together.
## How it works ## How it works
When you declare a model in your code, Qt will infer from the given input and output type what backend it will set up for the model. The backends are implemented as QPlugins. Currently, the backends are: When you declare a model in your code, Qt will infer from the given input and output type what backend it will set up for the model. The backends are implemented as QPlugins. Currently, the backends are:
| Input type | Output type | Qt backend | Description | | Input type | Output type | Qt backend | Description |
|------------|-------------|---------------|-------------------------------------------------------------------------------| |------------|-------------|-----------------|-------------------------------------------------------------------------------|
| Text\|Image| Text | QtOllamaModel | Uses ollama to load LLM models and communicate to them with ollama's REST API | | Text\|Image| Text | QtOllamaModel | Uses ollama to load LLM models and communicate to them with ollama's REST API |
| Speech | Text | QtAsrModel | Uses Whisper for Automatic Speech Recognition (ASR), or speech-to-text | | Speech | Text | QtAsrModel | Uses Whisper for Automatic Speech Recognition (ASR), or speech-to-text |
| Image | Json | QtTritonModel | Uses Triton to load a model for object detection from images | | Image | Json | QtTritonModel | Uses Triton to load a model for object detection from images |
| Image | Json | QtYoloModel | Uses a YOLO model for object detection from images | | Image | Json | QtYoloModel | Uses a YOLO model for object detection from images |
| Text | Speech | QtPiperModel | Uses Piper TTS model to convert text into speech | | Text | Speech | QtTtsModel | Uses QtTextToSpeech (QtSpeech) to convert text into speech |
| Text | Speech | QtPiperModel | Uses Piper TTS model to convert text into speech |
| Text | Image | QtDiffuserModel | Uses Diffusers to convert text into images |
Note, the Qt backends expect the underlying backend implementation (ollama, Whisper...) to be running, and will not take care of starting them up for you. You need to start them yourself, e.g. in the case of QtOllamaModel, loading the intended model to ollama's memory by running: Note, the Qt backends expect the underlying backend implementation (ollama, Whisper...) to be running, and will not take care of starting them up for you. You need to start them yourself, e.g. in the case of QtOllamaModel, loading the intended model to ollama's memory by running:
``` ```
...@@ -51,10 +56,10 @@ A combination of AiModelType flags to tell what type of model to instantiate. Po ...@@ -51,10 +56,10 @@ A combination of AiModelType flags to tell what type of model to instantiate. Po
| InputImage | 0x00008 | The model takes image as input | | InputImage | 0x00008 | The model takes image as input |
| InputJson | 0x00010 | The model takes JSON as input | | InputJson | 0x00010 | The model takes JSON as input |
| OutputText | 0x00100 | The model outputs text | | OutputText | 0x00100 | The model outputs text |
| OutputAudio | 0x00200 |The model outputs speech | | OutputAudio | 0x00200 | The model outputs speech |
| OutputVideo | 0x00400 |The model outputs video | | OutputVideo | 0x00400 | The model outputs video |
| OutputImage | 0x00800 |The model outputs image | | OutputImage | 0x00800 | The model outputs image |
| OutputJson | 0x01000 |The model outputs JSON | | OutputJson | 0x01000 | The model outputs JSON |
For supported input-output combinations, see the table under "How it works" section. For supported input-output combinations, see the table under "How it works" section.
...@@ -121,7 +126,7 @@ MultiModal { ...@@ -121,7 +126,7 @@ MultiModal {
| Read method: | QString model() | | Read method: | QString model() |
| Notifier signal: | void modelChanged() | | Notifier signal: | void modelChanged() |
**QVariantList rag** **QVariantList documents**
Retrieval-Augmented Generation data to use for the model, if it supports it. RAG supports currently only chromadb, which should be running on background. Retrieval-Augmented Generation data to use for the model, if it supports it. RAG supports currently only chromadb, which should be running on background.
...@@ -134,7 +139,7 @@ import qtaimodel ...@@ -134,7 +139,7 @@ import qtaimodel
type: (MultiModal.InputText | MultiModal.OutputText) type: (MultiModal.InputText | MultiModal.OutputText)
model: "llama3.2" model: "llama3.2"
prompt: "Which item has best armor bonus?" prompt: "Which item has best armor bonus?"
rag: ["Cloth of Authority | Armour Class +1", documents: ["Cloth of Authority | Armour Class +1",
"Drunken Cloth | Constitution +2 (up to 20)", "Drunken Cloth | Constitution +2 (up to 20)",
"Icebite Robe | Resistance to Damage Types: Cold damage.", "Icebite Robe | Resistance to Damage Types: Cold damage.",
"Obsidian Laced Robe | Grants Resistance to Damage Types: Fire damage.", "Obsidian Laced Robe | Grants Resistance to Damage Types: Fire damage.",
...@@ -145,11 +150,34 @@ import qtaimodel ...@@ -145,11 +150,34 @@ import qtaimodel
| | | | | |
|------------------|-----------------------------------------------------| |------------------|-----------------------------------------------------|
| Write method: | void setRag(QByteArray) | | Write method: | void setDocuments(QVariantList) |
| Read method: | QByteArray rag() | | Read method: | QVariantList documents() |
| Notifier signal: | void ragChanged() | | Notifier signal: | void documentsChanged() |
**int seed**
Seed to use with model prompts. Seed reduces randomness in model answers.
Example:
```
import qtaimodel
MultiModal {
id: llamaModel
type: (MultiModal.InputText | MultiModal.OutputText)
model: "gemma3"
prompt: "Say hello?"
seed: 3453654
}
```
| | |
|------------------|-----------------------------------------------------|
| Write method: | void setDocuments(QByteArray) |
| Read method: | QByteArray documents() |
| Notifier signal: | void documentsChanged() |
**QVector<QAiModel*> inputs** **QVector<QAiModel\*> inputs**
A list of models this model will use as its inputs. This allows for chaining models together to create pipelines. You can use the Optional flag with the model's type to tell whether it's an optional or mandatory input. For mandatory inputs, this model will not process any other inputs before the mandatory one has something to offer. For optional ones, other inputs will be processed regardless if that input has data available or not. A list of models this model will use as its inputs. This allows for chaining models together to create pipelines. You can use the Optional flag with the model's type to tell whether it's an optional or mandatory input. For mandatory inputs, this model will not process any other inputs before the mandatory one has something to offer. For optional ones, other inputs will be processed regardless if that input has data available or not.
......
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
// TODO: check if all is really needed here
import QtQuick 2.15
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.platform // TODO: Is this used here?
import QtMultimedia
import qtaimodel
Rectangle {
id: rectangle
anchors.fill: parent
color: "#ffffff" // TODO: Use some better color?
property string llamaPrompt: "You are an assistant.\n"
property string imageFile: ""
ColumnLayout {
RowLayout {
id: buttonRow
Button {
text: "Record audio"
onClicked: {
recorder.record()
}
}
Button {
text: "Stop audio recording"
onClicked: {
recorder.stop()
if (recorder.actualLocation != "") {
speechToText.pushData(recorder.actualLocation)
}
if (imageFile != "") {
imageToText.pushData(imageFile)
}
}
}
}
RowLayout {
Button {
text: qsTr("Open image")
onClicked: fileDialog.open()
}
Text {
id: result
text: rectangle.imageFile
}
}
TextField {
text: llamaPrompt
implicitWidth: 300
onEditingFinished: llamaModel.prompt = text
}
TextArea {
placeholderText: "Enter context"
background: Rectangle {
color: "lightgreen"
}
implicitWidth: 300
implicitHeight: 200
onEditingFinished: llamaModel.rag = [text]
}
Image {
source: imageFile
}
}
FileDialog {
id: fileDialog
folder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
nameFilters: ["*.*"]
onAccepted: {
imageFile = fileDialog.file
}
onRejected: {}
}
CaptureSession {
audioInput: AudioInput {}
recorder: MediaRecorder {
id: recorder
mediaFormat {
fileFormat: MediaFormat.Wave
}
}
}
MultiModal {
id: imageToText
type: (MultiModal.InputImage | MultiModal.OutputText)
model: "llava-phi3" // TODO: replace with Janus model from DeepSeek
prompt: "What is in the picture?"
optional: true
buffered: true
}
MultiModal {
id: speechToText
type: (MultiModal.InputAudio | MultiModal.OutputText)
model: "turbo"
}
MultiModal {
id: llamaModel
type: (MultiModal.InputText | MultiModal.OutputText)
model: "gemma3:4b"
prompt: llamaPrompt
inputs: [ imageToText, speechToText ]
}
MultiModal {
id: text2speech
type: (MultiModal.InputText | MultiModal.OutputAudio)
inputs: [ llamaModel ]
}
}
...@@ -19,25 +19,26 @@ add_subdirectory(plugins) ...@@ -19,25 +19,26 @@ add_subdirectory(plugins)
find_package(Qt6 6.8 REQUIRED COMPONENTS Core Qml Quick Network) find_package(Qt6 6.8 REQUIRED COMPONENTS Core Qml Quick Network)
qt_standard_project_setup(REQUIRES 6.8) qt_standard_project_setup(REQUIRES 6.8)
qt_add_library(QtAiModelPluginInterface qt_add_library(QtAiModelPluginInterface SHARED
qaimodelinterface_p.h qaimodelinterface_p.h qtaiapiexports_p.h
chromadb.h chromadb.cpp chromadb.h chromadb.cpp
) )
target_compile_definitions(QtAiModelPluginInterface PRIVATE QTAIAPI_LIBRARY)
target_link_libraries(QtAiModelPluginInterface target_link_libraries(QtAiModelPluginInterface
PRIVATE PRIVATE
Qt6::Core Qt6::Core
Qt6::Network Qt6::Network
) )
qt_add_qml_module(QtAiModelApi qt_add_qml_module(QtAiModelApi STATIC
URI qtaimodel URI qtaimodel
VERSION 1.0 VERSION 1.0
SHARED
SOURCES SOURCES
qaimodel.h qaimodel.cpp qaimodel.h qaimodel.cpp
) )
qt_import_qml_plugins(QtAiModelApi)
target_link_libraries(QtAiModelApi target_link_libraries(QtAiModelApi
PRIVATE PRIVATE
......
...@@ -25,7 +25,7 @@ void ChromaDb::sendRequest( ...@@ -25,7 +25,7 @@ void ChromaDb::sendRequest(
if (reply.isHttpStatusSuccess()) { if (reply.isHttpStatusSuccess()) {
lambda(json ? json.value() : QJsonDocument(), reply.httpStatus()); lambda(json ? json.value() : QJsonDocument(), reply.httpStatus());
} else { } else {
qDebug() << "JSON decode error:" << request.url() << "HTTP status:" << reply.httpStatus(); qDebug() << request.url() << "responded with error:" << reply.errorString();
setError(true); setError(true);
} }
}); });
...@@ -74,7 +74,7 @@ void ChromaDb::reset() ...@@ -74,7 +74,7 @@ void ChromaDb::reset()
connect(true); connect(true);
} }
} else { } else {
qDebug() << url << "deleted"; qDebug() << url << "responded with error:" << reply.errorString();
} }
}); });
......
...@@ -6,8 +6,9 @@ ...@@ -6,8 +6,9 @@
#include <QObject> #include <QObject>
#include <QRestAccessManager> #include <QRestAccessManager>
#include "qtaiapiexports_p.h"
class ChromaDb : public QObject class QTAIAPI_EXPORT ChromaDb : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool connected READ connected WRITE connect NOTIFY connectedChanged FINAL) Q_PROPERTY(bool connected READ connected WRITE connect NOTIFY connectedChanged FINAL)
......
#!/usr/bin/python3
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
from http.server import BaseHTTPRequestHandler,HTTPServer
from os import curdir, sep
import torch
from diffusers import AutoPipelineForText2Image, LCMScheduler
import simplejson
import base64
from io import BytesIO
from PIL import Image
PORT_NUMBER = 8005
#This class will handles any incoming request from
#the browser
class myHandler(BaseHTTPRequestHandler):
model = ''
#Handler for the POST requests
def do_POST(self):
print("do_POST");
if self.path=="/send":
self.data_string = self.rfile.read(int(self.headers['Content-Length']))
print("data_string: " + self.data_string.decode())
json_data = simplejson.loads(self.data_string)
#print("json_data: " + simplejson.dumps(json_data))
print("MODEL: " + json_data["model"])
if self.model != json_data["model"]:
self.pipeline = AutoPipelineForText2Image.from_pretrained(
json_data["model"],
#'black-forest-labs/FLUX.1-dev',
#'IDKiro/sdxs-512-dreamshaper',
#"stabilityai/stable-diffusion-2-1-base",
#'black-forest-labs/FLUX.1-schnell',
torch_dtype=torch.float32,
#variant="fp16",
use_safetensor=True).to('cpu')
self.model = json_data["model"]
image = self.pipeline(json_data["prompt"],
height=512,
width=512,
guidance_scale=0.0,
target_size=(1024, 1024),
original_size=(4096, 4096),
num_inference_steps=1
#max_sequence_length=256
).images[0]
buffered = BytesIO()
image.save(buffered, format="PNG")
b64image = base64.b64encode(buffered.getvalue())
print("Sending response")
self.send_response(200)
self.end_headers()
json_response = {}
json_response["image"] = b64image
self.wfile.write(simplejson.dumps(json_response).encode("utf-8"))
return
try:
#Create a web server and define the handler to manage the
#incoming request
server = HTTPServer(('', PORT_NUMBER), myHandler)
print('Started httpserver on port ' , PORT_NUMBER)
#Wait forever for incoming htto requests
server.serve_forever()
except KeyboardInterrupt:
print('^C received, shutting down the web server')
server.socket.close()
...@@ -4,3 +4,4 @@ add_subdirectory(ollama) ...@@ -4,3 +4,4 @@ add_subdirectory(ollama)
add_subdirectory(triton) add_subdirectory(triton)
add_subdirectory(yolo) add_subdirectory(yolo)
add_subdirectory(piper-tts) add_subdirectory(piper-tts)
add_subdirectory(diffuser)
...@@ -5,7 +5,7 @@ qt_add_plugin(QtAsrModel ...@@ -5,7 +5,7 @@ qt_add_plugin(QtAsrModel
qasraimodel_p.h qasraimodel_p.cpp qasraimodel_p.h qasraimodel_p.cpp
) )
set_target_properties(QtAsrModel PROPERTIES set_target_properties(QtAsrModel PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/plugins" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/aimodel"
) )
target_link_libraries(QtAsrModel target_link_libraries(QtAsrModel
PRIVATE PRIVATE
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#include "qaimodel.h" #include "qaimodel.h"
#include "qasraimodel_p.h" #include "qasraimodel_p.h"
#include <QDir>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QNetworkReply> #include <QNetworkReply>
...@@ -15,12 +16,12 @@ QAsrAiModel::QAsrAiModel() ...@@ -15,12 +16,12 @@ QAsrAiModel::QAsrAiModel()
{ {
} }
void QAsrAiModel::pushData(QVariantList data) void QAsrAiModel::pushData(QVariantList data, int seed)
{ {
qDebug() << "QAsrAiModel::pushData(): data:" << data; qDebug() << "QAsrAiModel::pushData(): data:" << data;
if (data.isEmpty() || data.first().toUrl().isEmpty()) { if (data.isEmpty() || data.first().toUrl().isEmpty()) {
emit dataReceived(data.first().toUrl()); emit dataReceived(data.first().toUrl().toLocalFile());
return; return;
} }
...@@ -29,7 +30,7 @@ void QAsrAiModel::pushData(QVariantList data) ...@@ -29,7 +30,7 @@ void QAsrAiModel::pushData(QVariantList data)
QJsonDocument doc; QJsonDocument doc;
QJsonObject obj = doc.object(); QJsonObject obj = doc.object();
obj["model"] = m_owner->model(); obj["model"] = m_owner->model();
obj["file"] = data.first().toUrl().path(); obj["file"] = data.first().toUrl().toLocalFile();
//obj["stream"] = false; //obj["stream"] = false;
doc.setObject(obj); doc.setObject(obj);
m_restApi.post(request, doc.toJson(), this, [this](QRestReply &reply) { m_restApi.post(request, doc.toJson(), this, [this](QRestReply &reply) {
......
...@@ -13,7 +13,7 @@ class QAsrAiModel : public AiModelPrivateInterface ...@@ -13,7 +13,7 @@ class QAsrAiModel : public AiModelPrivateInterface
Q_OBJECT Q_OBJECT
public: public:
QAsrAiModel(); QAsrAiModel();
void pushData(QVariantList data) override; void pushData(QVariantList data, int seed) override;
private: private:
QNetworkAccessManager m_manager; QNetworkAccessManager m_manager;
......
find_package(Qt6 REQUIRED COMPONENTS Core Network Quick)
qt_add_plugin(QtDiffuserModel
CLASS_NAME QAiModelPluginFactory
qdiffuseraimodel_p.h qdiffuseraimodel_p.cpp
)
set_target_properties(QtDiffuserModel PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/aimodel"
)
target_link_libraries(QtDiffuserModel
PRIVATE
Qt6::Core
Qt6::Network
Qt6::Quick
QtAiModelPluginInterface)
include_directories(../..)
{ "name": "diffuserplugin",
"supportedTypes": ["InputText", "OutputImage"]
}
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
#include "qaimodel.h"
#include "qdiffuseraimodel_p.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QRestReply>
#include <QImage>
QDiffuserAiModel::QDiffuserAiModel()
: AiModelPrivateInterface(),
m_manager(this)
, m_restApi(&m_manager)
{
}
void QDiffuserAiModel::pushData(QVariantList data, int seed)
{
qDebug() << "QDiffuserAiModel::pushData(): data:" << data;
if (data.isEmpty() || data.first().toString().isEmpty()) {
emit dataReceived(data.first().toString());
return;
}
QNetworkRequest request(QUrl("http://localhost:8005/send"));
request.setRawHeader("Content-Type", "application/json");
QJsonDocument doc;
QJsonObject obj = doc.object();
obj["model"] = m_owner->model();
obj["prompt"] = data.first().toString();
doc.setObject(obj);
qDebug() << doc.toJson();
m_restApi.post(request, doc.toJson(), this, [this](QRestReply &reply) {
if (auto json = reply.readJson()) {
emit dataReceived(QUrl(
QString("data:image/png;base64,") + json->object()["image"].toString().toUtf8()));
}
});
}
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
#ifndef QDIFFUSERAIMODEL_P_H
#define QDIFFUSERAIMODEL_P_H
#include <QObject>
#include <QRestAccessManager>
#include "qaimodelinterface_p.h"
class QDiffuserAiModel : public AiModelPrivateInterface
{
Q_OBJECT
public:
QDiffuserAiModel();
void pushData(QVariantList data, int seed) override;
private:
QNetworkAccessManager m_manager;
QRestAccessManager m_restApi;
};
class QDiffuserAiModelPlugin : public QAiModelPluginFactory
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QAiModelPluginFactory/1.0" FILE "plugin.json")
Q_INTERFACES(QAiModelPluginFactory)
public:
QDiffuserAiModelPlugin() {}
AiModelPrivateInterface* createInterface() { return new QDiffuserAiModel(); }
};
#endif // QDIFFUSERAIMODEL_P_H
...@@ -6,7 +6,7 @@ qt_add_plugin(QtOllamaModel ...@@ -6,7 +6,7 @@ qt_add_plugin(QtOllamaModel
) )
set_target_properties(QtOllamaModel PROPERTIES set_target_properties(QtOllamaModel PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/plugins" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/aimodel"
) )
target_link_libraries(QtOllamaModel target_link_libraries(QtOllamaModel
PRIVATE PRIVATE
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValue>
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QRestReply> #include <QRestReply>
...@@ -35,7 +36,9 @@ static inline void sendRequest( ...@@ -35,7 +36,9 @@ static inline void sendRequest(
QNetworkRequest request(url); QNetworkRequest request(url);
request.setRawHeader("Content-Type", "application/json"); request.setRawHeader("Content-Type", "application/json");
restApi->post(request, QJsonDocument(object).toJson(), owner, [=](QRestReply &reply) { restApi->post(request, QJsonDocument(object).toJson(), owner, [=](QRestReply &reply) {
if (std::optional<QJsonDocument> json = reply.readJson()) { if (!reply.isHttpStatusSuccess()) {
qDebug() << request.url() << "responded with error" << reply.errorString() << " and status:" << reply.httpStatus();
} else if (std::optional<QJsonDocument> json = reply.readJson()) {
lambda(json.value()); lambda(json.value());
} else { } else {
qDebug() << "Error. No data received from" << request.url() << reply; qDebug() << "Error. No data received from" << request.url() << reply;
...@@ -43,7 +46,7 @@ static inline void sendRequest( ...@@ -43,7 +46,7 @@ static inline void sendRequest(
}); });
} }
void QLlmAiModel::pushData(QVariantList data) void QLlmAiModel::pushData(QVariantList data, int seed)
{ {
QString query = m_owner->prompt(); QString query = m_owner->prompt();
QJsonArray images; QJsonArray images;
...@@ -52,7 +55,7 @@ void QLlmAiModel::pushData(QVariantList data) ...@@ -52,7 +55,7 @@ void QLlmAiModel::pushData(QVariantList data)
query.append(QString::fromLatin1(i.toByteArray())); query.append(QString::fromLatin1(i.toByteArray()));
if (i.canConvert<QUrl>()) { if (i.canConvert<QUrl>()) {
QFile file(QUrl(i.toUrl()).path()); QFile file(QUrl(i.toUrl()).toLocalFile());
if (file.open(QIODevice::ReadOnly) != 0) { if (file.open(QIODevice::ReadOnly) != 0) {
QByteArray ba = file.readAll(); QByteArray ba = file.readAll();
QByteArray ba2 = ba.toBase64(); QByteArray ba2 = ba.toBase64();
...@@ -63,13 +66,16 @@ void QLlmAiModel::pushData(QVariantList data) ...@@ -63,13 +66,16 @@ void QLlmAiModel::pushData(QVariantList data)
} }
qDebug() << this << "[\"prompt\"]: " << query << "[images]" << images.count(); qDebug() << this << "[\"prompt\"]: " << query << "[images]" << images.count();
auto promptResponseReceived = [=](auto json) { auto promptResponseReceived = [=](auto json) {
emit dataReceived(json.object()["response"].toString().toUtf8()); emit dataReceived(json.object()["response"].toString().toUtf8());
}; };
QPair<QString, QJsonValue> options;
if (seed != 0) {
options = {"options", QJsonObject({{"seed", {seed}}})};
}
if (m_chromadb.connected()) { if (m_chromadb.connected()) {
connect(&m_chromadb, &ChromaDb::embeddingsFound, this, [=](auto embeddings) { connect(&m_chromadb, &ChromaDb::embeddingsFound, this, [=](auto embeddings) {
QString documents; QString documents;
...@@ -83,14 +89,19 @@ void QLlmAiModel::pushData(QVariantList data) ...@@ -83,14 +89,19 @@ void QLlmAiModel::pushData(QVariantList data)
qDebug() << q; qDebug() << q;
sendRequest(&m_restApi, sendRequest(&m_restApi,
m_ollama_url_base + "generate", m_ollama_url_base + "generate",
QJsonObject({{"model", m_owner->model()}, {"prompt", q}, {"stream", false}}), QJsonObject({{"model", m_owner->model()},
{"prompt", q},
{"stream", false},
options}),
this, this,
promptResponseReceived); promptResponseReceived);
}, Qt::SingleShotConnection); }, Qt::SingleShotConnection);
sendRequest(&m_restApi, sendRequest(&m_restApi,
m_ollama_url_base + "embed", m_ollama_url_base + "embed",
QJsonObject({{"model", m_owner->model()}, {"input", query}}), QJsonObject({{"model", m_owner->model()},
{"input", query},
options}),
this, this,
[this](auto json) { [this](auto json) {
m_chromadb.fetchEmbeddings(json.object()["embeddings"].toArray().toVariantList()); m_chromadb.fetchEmbeddings(json.object()["embeddings"].toArray().toVariantList());
...@@ -101,7 +112,8 @@ void QLlmAiModel::pushData(QVariantList data) ...@@ -101,7 +112,8 @@ void QLlmAiModel::pushData(QVariantList data)
QJsonObject({{"model", m_owner->model()}, QJsonObject({{"model", m_owner->model()},
{"prompt", query}, {"prompt", query},
{"stream", false}, {"stream", false},
{"images", images}}), {"images", images},
options}),
this, this,
promptResponseReceived); promptResponseReceived);
} }
......
...@@ -14,7 +14,7 @@ class QLlmAiModel : public AiModelPrivateInterface ...@@ -14,7 +14,7 @@ class QLlmAiModel : public AiModelPrivateInterface
Q_OBJECT Q_OBJECT
public: public:
QLlmAiModel(); QLlmAiModel();
void pushData(QVariantList data) override; void pushData(QVariantList data, int seed) override;
void setRag(QVariantList data) override; void setRag(QVariantList data) override;
private: private:
......
...@@ -6,7 +6,7 @@ qt_add_plugin(QtPiperModel ...@@ -6,7 +6,7 @@ qt_add_plugin(QtPiperModel
) )
set_target_properties(QtPiperModel PROPERTIES set_target_properties(QtPiperModel PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/plugins" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/aimodel"
) )
target_link_libraries(QtPiperModel target_link_libraries(QtPiperModel
PRIVATE PRIVATE
......
{ "name": "ttsplugin", { "name": "ttspiperplugin",
"supportedTypes": ["InputText", "OutputAudio"] "supportedTypes": ["InputText", "OutputAudio"]
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment