Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
machinetranslator.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5#include "ollama.h"
7
8#include <QNetworkRequest>
9#include <QNetworkReply>
10#include <QNetworkAccessManager>
11
12using namespace Qt::Literals::StringLiterals;
13
14QT_BEGIN_NAMESPACE
15
16MachineTranslator::MachineTranslator()
17 : m_request(std::make_unique<QNetworkRequest>()),
18 m_manager(std::make_unique<QNetworkAccessManager>()),
19 m_translator(std::make_unique<Ollama>())
20{
21 m_request->setHeader(QNetworkRequest::ContentTypeHeader, "application/json"_L1);
22 m_request->setTransferTimeout(5 * 60 * 1000);
23}
24
26
27void MachineTranslator::translate(const Messages &messages, const QString &userContext)
28{
29 auto batches = m_translator->makeBatches(messages, userContext);
30 for (auto &b : std::as_const(batches))
31 translateBatch(b, 0);
32}
33
34void MachineTranslator::setUrl(const QString &url)
35{
36 m_translator->setUrl(url);
37 m_request->setUrl(m_translator->translationEndpoint());
38}
39
40void MachineTranslator::activateTranslationModel(const QString &modelName)
41{
42 if (auto wakeupPayload = m_translator->stageModel(modelName)) {
43 // after several minutes of being idle, Ollama offloads the model
44 // and the rest API needs to be waken up. Trying to connect with
45 // Ollama for the first time after several minutes wakes up the
46 // server, but for some reason the server doesn't queue the
47 // connection request that was sent to it while it was not awake. As a result,
48 // the connection in QNetworkAccessManager is half broken and not working,
49 // without us knowing about it.
50 // Here we are using a new QNetworkAccessManager instance to send
51 // the first connection request and wake up the model. Then we delete the
52 // QNetworkAccessManager instance since it contains a broken connection
53 // and will try to use it for the next requests otherwise.
54
55 auto *tempManager = new QNetworkAccessManager(this);
56
57 QNetworkRequest wakeupRequest(m_translator->translationEndpoint());
58 wakeupRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"_L1);
59 wakeupRequest.setTransferTimeout(30000);
60
61 QNetworkReply *reply = tempManager->post(wakeupRequest, *wakeupPayload);
62
63 connect(reply, &QNetworkReply::finished, this, [tempManager, reply]() {
64 reply->deleteLater();
65 tempManager->deleteLater();
66 });
67 }
68}
69
71{
72 QNetworkRequest req(m_translator->discoveryEndpoint());
73 QNetworkReply *reply = m_manager->get(req);
74 connect(reply, &QNetworkReply::finished, this, [this, reply]() {
75 reply->deleteLater();
76 QStringList models;
77 if (reply->error() == QNetworkReply::NoError) {
78 const QByteArray response = reply->readAll();
79 models = m_translator->extractModels(response);
80 }
81 emit modelsReceived(std::move(models));
82 });
83}
84
85void MachineTranslator::translateBatch(Batch b, int tries)
86{
87 if (m_stopped)
88 return;
89 const QByteArray body = m_translator->payload(b);
90 QNetworkReply *reply = m_manager->post(*m_request, body);
91 connect(reply, &QNetworkReply::finished, this,
92 [this, reply, batch = std::move(b), session = m_session.load(), tries] {
93 translationReceived(reply, std::move(batch), tries, session);
94 });
95}
96
97void MachineTranslator::translationReceived(QNetworkReply *reply, Batch b, int tries, int session)
98{
99 reply->deleteLater();
100 if (m_stopped || session != m_session.load())
101 return;
102
103 if (reply->error() != QNetworkReply::NoError) {
104 if (tries < s_maxTries
105 && (reply->error() == QNetworkReply::OperationCanceledError
106 || reply->error() == QNetworkReply::TimeoutError
107 || reply->error() == QNetworkReply::UnknownNetworkError)) {
108 translateBatch(std::move(b), tries + 1);
109 return;
110 }
111
112 QList<const TranslatorMessage *> failed;
113 for (const auto &i : std::as_const(b.items))
114 failed.append(i.msg);
115 emit translationFailed(std::move(failed));
116 return;
117 }
118
119 const QByteArray response = reply->readAll();
120 QList<Item> items = std::move(b.items);
121 QHash<const TranslatorMessage *, QString> out;
122 const QHash<QString, QString> translations = m_translator->extractTranslations(response);
123 for (Item &i : items) {
124 if (i.msg->translation().isEmpty()) {
125 if (QString translation = translations[i.msg->sourceText()]; !translation.isEmpty())
126 out[i.msg] = std::move(translation);
127 else
128 b.items.append(std::move(i));
129 }
130 }
131
132 if (out.empty() && tries == s_maxTries) {
133 QList<const TranslatorMessage *> failed;
134 for (const auto &i : std::as_const(b.items))
135 failed.append(i.msg);
136 emit translationFailed(std::move(failed));
137 } else if (out.empty() && tries < s_maxTries) {
138 translateBatch(std::move(b), tries + 1);
139 } else {
140 emit batchTranslated(std::move(out));
141 if (!b.items.empty())
142 translateBatch(std::move(b), tries);
143 }
144}
145
146QT_END_NAMESPACE
void setUrl(const QString &url)
void translate(const Messages &messages, const QString &userContext=QString())
void activateTranslationModel(const QString &modelName)