11#include <QtNetwork/qnetworkaccessmanager.h>
12#include <QtNetwork/qnetworkreply.h>
13#include <QtNetwork/qnetworkrequest.h>
15using namespace Qt::Literals::StringLiterals;
19MachineTranslator::MachineTranslator()
20 : m_request(std::make_unique<QNetworkRequest>()),
21 m_manager(std::make_unique<QNetworkAccessManager>())
23 m_request->setHeader(QNetworkRequest::ContentTypeHeader,
"application/json"_L1);
24 m_request->setTransferTimeout(TranslationSettings::transferTimeoutMs());
30 case TranslationApiType::Ollama:
31 m_translator = std::make_unique<Ollama>();
33 case TranslationApiType::OpenAICompatible:
34 m_translator = std::make_unique<OpenAICompatible>();
43 QMutexLocker locker(&m_queueMutex);
45 m_pendingBatches.clear();
46 auto batches = m_translator->makeBatches(messages, userContext);
48 for (
auto &b : batches)
49 m_pendingBatches.enqueue(std::move(b));
56 QMutexLocker locker(&m_queueMutex);
57 m_pendingBatches.clear();
64 m_translator->setUrl(url);
65 m_request->setUrl(m_translator->translationEndpoint());
70 if (!apiKey.isEmpty())
71 m_request->setRawHeader(
"Authorization",
"Bearer " + apiKey.toUtf8());
73 m_request->setRawHeader(
"Authorization", QByteArray());
78 if (
auto wakeupPayload = m_translator->stageModel(modelName)) {
91 auto *tempManager =
new QNetworkAccessManager(
this);
93 QNetworkRequest wakeupRequest(m_translator->translationEndpoint());
94 wakeupRequest.setHeader(QNetworkRequest::ContentTypeHeader,
"application/json"_L1);
95 wakeupRequest.setTransferTimeout(30000);
97 QNetworkReply *reply = tempManager->post(wakeupRequest, *wakeupPayload);
99 connect(reply, &QNetworkReply::finished,
this, [tempManager, reply]() {
100 reply->deleteLater();
101 tempManager->deleteLater();
108 QNetworkRequest req(m_translator->discoveryEndpoint());
110 if (m_request->hasRawHeader(
"Authorization"))
111 req.setRawHeader(
"Authorization", m_request->rawHeader(
"Authorization"));
112 QNetworkReply *reply = m_manager->get(req);
113 connect(reply, &QNetworkReply::finished,
this, [
this, reply]() {
114 reply->deleteLater();
116 if (reply->error() == QNetworkReply::NoError) {
117 const QByteArray response = reply->readAll();
118 models = m_translator->extractModels(response);
120 emit modelsReceived(std::move(models));
126 Q_ASSERT_X(!m_queueMutex.tryLock(), Q_FUNC_INFO,
127 "The function requires m_queueMutex to be held.");
131 const QByteArray body = m_translator->payload(b);
132 emit debugLog(body,
false);
133 QNetworkReply *reply = m_manager->post(*m_request, body);
134 connect(reply, &QNetworkReply::finished,
this,
135 [
this, reply, batch = std::move(b), session = m_session.load()] {
136 translationReceived(reply, std::move(batch), session);
142 Q_ASSERT_X(!m_queueMutex.tryLock(), Q_FUNC_INFO,
143 "The function requires m_queueMutex to be held.");
144 if (m_stopped || m_pendingBatches.isEmpty())
148 const int batchesToSchedule = qMin(maxConcurrent - m_inFlightCount, m_pendingBatches.size());
149 for (
int i = 0; i < batchesToSchedule; ++i) {
150 Batch batch = m_pendingBatches.dequeue();
151 translateBatch(std::move(batch));
157 reply->deleteLater();
159 if (m_stopped || session != m_session) {
160 QMutexLocker locker(&m_queueMutex);
162 processNextBatches();
166 bool shouldRetry =
false;
167 const QByteArray response = reply->readAll();
168 emit debugLog(response,
true);
171 if (reply->error() != QNetworkReply::NoError) {
172 const auto error = reply->error();
174 if (error == QNetworkReply::ProtocolInvalidOperationError)
175 m_translator->onRequestRejected();
177 const bool isRetriableError = error == QNetworkReply::OperationCanceledError
178 || error == QNetworkReply::TimeoutError
179 || error == QNetworkReply::UnknownNetworkError
180 || error == QNetworkReply::ProtocolInvalidOperationError;
181 shouldRetry = b
.tries < maxRetries && isRetriableError;
183 QList<
const TranslatorMessage *> failed;
184 for (
const auto &i : std::as_const(b.items))
185 failed.append(i.msg);
186 emit translationFailed(std::move(failed));
189 QList<Item> items = std::move(b.items);
190 QHash<
const TranslatorMessage *, QStringList> out;
191 QHash<QString, QStringList> translations =
192 m_translator->extractTranslations(response, b.pluralFormsCount > 1);
195 QList<Item> nonMatched;
196 for (Item &i : items) {
197 if (i.msg->translation().isEmpty()) {
198 if (
auto translation = translations.find(i.msg->sourceText());
199 translation != translations.end()) {
200 out[i.msg] = *translation;
201 translations.erase(translation);
203 nonMatched.append(std::move(i));
209 constexpr int similarityThreshold = 200;
210 for (Item &i : nonMatched) {
211 StringSimilarityMatcher matcher(i.msg->sourceText());
214 for (
auto it = translations.cbegin(); it != translations.cend(); ++it) {
215 const int score = matcher.getSimilarityScore(it.key());
216 if (score >= similarityThreshold && score > bestScore) {
218 bestMatch = it.key();
222 if (!bestMatch.isEmpty())
223 out[i.msg] = translations.take(bestMatch);
225 b.items.append(std::move(i));
228 const bool nonTranslatedItems = !b.items.empty();
229 shouldRetry = nonTranslatedItems && b
.tries < maxRetries;
230 if (nonTranslatedItems && !shouldRetry) {
231 QList<
const TranslatorMessage *> failed;
232 for (
const auto &i : std::as_const(b.items))
233 failed.append(i.msg);
234 emit translationFailed(std::move(failed));
237 emit batchTranslated(std::move(out));
240 QMutexLocker locker(&m_queueMutex);
244 m_pendingBatches.prepend(std::move(b));
246 processNextBatches();
void setUrl(const QString &url)
void translate(const Messages &messages, const QString &userContext=QString())
void activateTranslationModel(const QString &modelName)
void setApiType(TranslationApiType type)
void setApiKey(const QString &apiKey)
static int maxConcurrentBatches()