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
machinetranslationdialog.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 "ui_machinetranslationdialog.h"
6
7#include "messagemodel.h"
8#include "auto-translation/machinetranslator.h"
9#include "auto-translation/translationsettings.h"
10#include "globals.h"
11
12#include <QtCore/qsettings.h>
13#include <QtWidgets/qmessagebox.h>
14
15#include <array>
16
17using namespace Qt::Literals::StringLiterals;
18
19namespace {
20
21struct ApiTypeInfo
22{
24 const QLatin1String displayName;
25 const QLatin1String defaultUrl;
26 bool showApiKeyField;
27};
28
29// Central registry of all supported API types and their metadata
30constexpr ApiTypeInfo s_apiTypes[] = {
31 { TranslationApiType::Ollama, "Ollama"_L1, "http://localhost:11434"_L1, false },
32 { TranslationApiType::OpenAICompatible, "OpenAI Compatible"_L1, "http://localhost:8080"_L1,
33 true },
34};
35
36const ApiTypeInfo *findApiTypeInfo(TranslationApiType type)
37{
38 for (const auto &info : s_apiTypes) {
39 if (info.type == type)
40 return &info;
41 }
42 return nullptr;
43}
44
45} // namespace
46
47QT_BEGIN_NAMESPACE
48
49static constexpr std::array<const char *, 3> toolBoxTexts {
50 QT_TRANSLATE_NOOP("MachineTranslationDialog", "Configuration"),
51 QT_TRANSLATE_NOOP("MachineTranslationDialog", "Selection"),
52 QT_TRANSLATE_NOOP("MachineTranslationDialog", "Progress")
53};
54
55MachineTranslationDialog::MachineTranslationDialog(QWidget *parent)
56 : QDialog(parent),
57 m_ui(std::make_unique<Ui::MachineTranslationDialog>()),
58 m_translator(std::make_unique<MachineTranslator>())
59{
60 m_ui->setupUi(this);
61
62 updateToolBoxTexts();
63 connect(m_ui->toolBox, &QToolBox::currentChanged, this, &MachineTranslationDialog::updateToolBoxTexts);
64
65 m_ui->statusLabel->setWordWrap(true);
66 m_ui->statusLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
67 connect(m_ui->translateButton, &QPushButton::clicked, this,
68 &MachineTranslationDialog::translateSelection);
69 connect(m_ui->filesComboBox, &QComboBox::currentIndexChanged, this, [this] {
70 m_ui->filterComboBox->setCurrentIndex(0);
71 updateStatus();
72 });
73 connect(m_ui->groupListWidget, &QListWidget::itemSelectionChanged, this,
74 [this] { updateStatus(); });
75 connect(m_translator.get(), &MachineTranslator::batchTranslated, this,
76 &MachineTranslationDialog::onBatchTranslated);
77 connect(m_translator.get(), &MachineTranslator::translationFailed, this,
78 &MachineTranslationDialog::onTranslationFailed);
79 connect(m_ui->doneButton, &QPushButton::clicked, this, [this] {
80 if (discardTranslations())
81 accept();
82 });
83 connect(m_ui->cancelButton, &QPushButton::clicked, this, [this] {
84 if (discardTranslations())
85 reject();
86 });
87 connect(m_ui->applyButton, &QPushButton::clicked, this,
88 &MachineTranslationDialog::applyTranslations);
89
90 connect(m_ui->stopButton, &QPushButton::clicked, this, &MachineTranslationDialog::stop);
91 connect(m_ui->connectButton, &QPushButton::clicked, this,
92 &MachineTranslationDialog::connectToServer);
93 connect(m_translator.get(), &MachineTranslator::modelsReceived, this,
94 &MachineTranslationDialog::onModelsReceived);
95 connect(m_ui->modelComboBox, &QComboBox::currentTextChanged, this, [](const QString &text) {
96 if (!text.isEmpty())
97 QSettings().setValue(settingPath(selectedModelSettingsKey), text);
98 });
99 connect(this, &QDialog::finished, m_translator.get(), &MachineTranslator::stop);
100 connect(m_ui->filterComboBox, &QComboBox::currentIndexChanged, this,
101 &MachineTranslationDialog::onFilterChanged);
102 connect(m_ui->serverText, &QLineEdit::textChanged, this,
103 &MachineTranslationDialog::onServerUrlChanged);
104 connect(m_ui->apiKeyEdit, &QLineEdit::textChanged, this, [this] {
105 if (m_connectionState == ConnectionState::Connected)
106 setConnectionState(ConnectionState::Modified);
107 });
108 connect(m_ui->apiTypeComboBox, &QComboBox::currentIndexChanged, this,
109 &MachineTranslationDialog::onApiTypeChanged);
110 connect(m_ui->logButton, &QPushButton::clicked, this, [this] {
111 if (m_ui->logButton->isChecked())
112 connect(m_translator.get(), &MachineTranslator::debugLog, this,
113 &MachineTranslationDialog::onNewDebugMessage);
114 else
115 disconnect(m_translator.get(), &MachineTranslator::debugLog, this,
116 &MachineTranslationDialog::onNewDebugMessage);
117 });
118 connect(m_ui->nextButton, &QPushButton::clicked, this,
119 [this] { m_ui->toolBox->setCurrentIndex(1); });
120
121 setConnectionState(ConnectionState::NotConnected);
122
123 // Populate API type combo box from registry
124 m_ui->apiTypeComboBox->clear();
125 for (const auto &info : s_apiTypes) {
126 m_ui->apiTypeComboBox->addItem(QString::fromLatin1(info.displayName),
127 QVariant::fromValue(info.type));
128 }
129
130 // Restore saved API type selection
131 QSettings config;
132 const int savedApiType = config.value(settingPath(selectedApiTypeSettingsKey),
133 static_cast<int>(TranslationApiType::Ollama))
134 .toInt();
135 for (int i = 0; i < m_ui->apiTypeComboBox->count(); ++i) {
136 if (m_ui->apiTypeComboBox->itemData(i).toInt() == savedApiType) {
137 m_ui->apiTypeComboBox->setCurrentIndex(i);
138 break;
139 }
140 }
141
142 // Advanced settings
143 loadAdvancedSettings();
144 validateAdvancedSettings();
145 toggleAdvancedSettings(false);
146
147 connect(m_ui->advancedSettingsToggle, &QPushButton::toggled, this,
148 &MachineTranslationDialog::toggleAdvancedSettings);
149 connect(m_ui->applySettingsButton, &QPushButton::clicked, this,
150 &MachineTranslationDialog::saveAdvancedSettings);
151 connect(m_ui->resetSettingsButton, &QPushButton::clicked, this,
152 &MachineTranslationDialog::resetAdvancedSettings);
153
154 // Validate when relevant settings change
155 connect(m_ui->maxRetriesSpinBox, &QSpinBox::valueChanged, this,
156 &MachineTranslationDialog::validateAdvancedSettings);
157 connect(m_ui->maxJsonFormatTriesSpinBox, &QSpinBox::valueChanged, this,
158 &MachineTranslationDialog::validateAdvancedSettings);
159
160 // Collapse advanced settings when leaving Configuration tab or closing dialog
161 connect(m_ui->toolBox, &QToolBox::currentChanged, this,
162 [this](int) { m_ui->advancedSettingsToggle->setChecked(false); });
163 connect(this, &QDialog::finished, this,
164 [this] { m_ui->advancedSettingsToggle->setChecked(false); });
165}
166
167void MachineTranslationDialog::updateToolBoxTexts()
168{
169 const int count = m_ui->toolBox->count();
170 Q_ASSERT(unsigned(count) == toolBoxTexts.size());
171 const int index = m_ui->toolBox->currentIndex();
172
173 for (int i = 0; i < count; ++i) {
174 const QString baseText = MachineTranslationDialog::tr(toolBoxTexts[i]);
175 m_ui->toolBox->setItemText(i, (i == index ? "- "_L1 : "+ "_L1) + baseText);
176 }
177}
178
180{
181 m_dataModel = dm;
182 refresh(true);
183}
184
185void MachineTranslationDialog::refresh(bool init)
186{
187 if (init) {
188 m_ui->toolBox->setCurrentIndex(0);
189 m_ui->filesComboBox->clear();
190 m_ui->filesComboBox->addItems(m_dataModel->srcFileNames());
191 m_ui->filesComboBox->setCurrentIndex(0);
192 m_ui->translationLog->setText(tr("Translation Log"));
193 m_ui->translateButton->setEnabled(true);
194 m_ui->stopButton->setEnabled(false);
195 connectToServer();
196 }
197 m_sentTexts = 0;
198 m_failedTranslations = 0;
199 m_receivedTranslations.clear();
200 m_ongoingTranslations.clear();
201 m_ui->applyButton->setEnabled(false);
202 m_ui->progressBar->setVisible(false);
203 m_translator->start();
204}
205
206void MachineTranslationDialog::logProgress(const QList<QStringList> &table)
207{
208 const qsizetype receivedCount = m_receivedTranslations.size();
209 m_ui->statusLabel->setText(
210 tr("Translation status: %1/%2 source texts translated, %3/%2 failed.")
211 .arg(receivedCount)
212 .arg(m_sentTexts)
213 .arg(m_failedTranslations));
214 const int progress =
215 m_sentTexts > 0 ? (receivedCount + m_failedTranslations) * 100 / m_sentTexts : 0;
216 m_ui->progressBar->setValue(progress);
217 if (!table.empty()) {
218 QString html = "<hr/><table cellpadding=\"4\""
219 "style=\""
220 "width:100%; "
221 "margin-left:10px; "
222 "\">"_L1;
223 for (const QStringList &row : table) {
224 html += "<tr>"_L1;
225 for (const QString &col : row)
226 html += "<td>%1</td>"_L1.arg(col);
227 html += "</tr>"_L1;
228 }
229 html += "</table>"_L1;
230 m_ui->translationLog->append(html);
231 }
232
233 if (receivedCount + m_failedTranslations == m_sentTexts) {
234 m_ui->translationLog->append(
235 tr("<hr/><b>Translation completed: %1/%2 translated, %3/%2 failed.</b>")
236 .arg(receivedCount)
237 .arg(m_sentTexts)
238 .arg(m_failedTranslations));
239 m_ui->translateButton->setEnabled(true);
240 m_ui->stopButton->setEnabled(false);
241 m_ui->applyButton->setEnabled(true);
242 m_ui->progressBar->setVisible(false);
243 } else {
244 m_ui->translateButton->setEnabled(false);
245 m_ui->stopButton->setEnabled(true);
246 m_ui->progressBar->setVisible(true);
247 }
248}
249
250void MachineTranslationDialog::logInfo(const QString &info)
251{
252 m_ui->translationLog->append("<hr/>"_L1);
253 m_ui->translationLog->append(info);
254}
255
256void MachineTranslationDialog::logWarning(const QString &warning)
257{
258 m_ui->translationLog->append(
259 "<span style=\"color:orange;\">%1</span>"_L1.arg(warning.toHtmlEscaped()));
260}
261
262void MachineTranslationDialog::logError(const QString &error)
263{
264 m_ui->translationLog->append("<hr/>"_L1);
265 m_ui->translationLog->append(
266 "<span style=\"color:red; font-weight: bold; \">%1</span>"_L1.arg(error));
267}
268
269bool MachineTranslationDialog::discardTranslations()
270{
271 return (m_receivedTranslations.empty()
272 || QMessageBox::warning(
273 this, tr("Qt Linguist"),
274 tr("%n translated item(s) will be discarded. Continue?", 0,
275 m_receivedTranslations.size()),
276 QMessageBox::Yes | QMessageBox::No)
277 == QMessageBox::Yes);
278}
279
280void MachineTranslationDialog::stop()
281{
282 m_translator->stop();
283 m_ui->stopButton->setEnabled(false);
284 m_ui->translateButton->setEnabled(true);
285 m_sentTexts = 0;
286 m_failedTranslations = 0;
287 m_ongoingTranslations.clear();
288 m_translator->start();
289 m_ui->applyButton->setEnabled(!m_receivedTranslations.empty());
290 m_ui->progressBar->setVisible(false);
291 logError(tr("Translation Stopped."));
292}
293
294void MachineTranslationDialog::translateSelection()
295{
296 m_ui->toolBox->setCurrentIndex(2);
297 const QString model = m_ui->modelComboBox->currentText();
298 const int id = m_ui->filesComboBox->currentIndex();
299 if (model.isEmpty()) {
300 logError(tr("Please verify the service URL is valid "
301 "and a translation model is selected."));
302 return;
303 }
304 if (id < 0) {
305 logError(tr("Please select a file for translation."));
306 return;
307 }
308 if (!discardTranslations())
309 return;
310 refresh(false);
311
312 const int filter = m_ui->filterComboBox->currentIndex();
313 const DataModel *dm = m_dataModel->model(id);
314 Messages messages;
315 if (filter == 0) {
316 QMutexLocker lock(&m_mutex);
319 if (tm->translation().isEmpty() && !tm->sourceText().isEmpty()) {
320 messages.items.append(tm);
321 m_ongoingTranslations[tm] =
322 MultiDataIndex{ it.translationType(), id, it.group(), it.message() };
323 }
324 }
325 for (DataModelIterator it(IDBASED, dm); it.isValid(); ++it) {
327 if (tm->translation().isEmpty() && !tm->sourceText().isEmpty()) {
328 messages.items.append(tm);
329 m_ongoingTranslations[tm] =
330 MultiDataIndex{ it.translationType(), id, it.group(), it.message() };
331 }
332 }
333 } else {
334 const QList<QListWidgetItem *> selectedItems = m_ui->groupListWidget->selectedItems();
335
336 if (selectedItems.isEmpty()) {
337 logError(tr("Please select at least one context/label to translate."));
338 return;
339 }
340
341 QMutexLocker lock(&m_mutex);
342 const auto type = (filter == 1) ? TEXTBASED : IDBASED;
343 for (QListWidgetItem *item : selectedItems) {
344 const int groupIdx = item->data(Qt::UserRole).toInt();
345 const GroupItem *g = dm->groupItem(groupIdx, type);
346 for (int i = 0; i < g->messageCount(); i++) {
347 const TranslatorMessage *tm = &g->messageItem(i)->message();
348 if (tm->translation().isEmpty() && !tm->sourceText().isEmpty()) {
349 messages.items.append(tm);
350 m_ongoingTranslations[tm] = MultiDataIndex{ type, id, groupIdx, i };
351 }
352 }
353 }
354 }
355
356 if (messages.items.isEmpty()) {
357 logInfo(tr("No items to translate. All selected messages already have translations."));
358 return;
359 }
360
361 messages.srcLang = QLocale::languageToString(dm->sourceLanguage());
362 messages.tgtLang = QLocale::languageToString(dm->language());
363 messages.pluralFormsCount = dm->numerusForms().size();
364 m_sentTexts += messages.items.size();
365 m_translator->activateTranslationModel(model);
366 m_translator->translate(messages, m_ui->contextEdit->toPlainText().trimmed());
367 logInfo(tr("Translation Started"));
368 logProgress({});
369}
370
371void MachineTranslationDialog::onBatchTranslated(
372 QHash<const TranslatorMessage *, QStringList> translations)
373{
374 QList<QStringList> log;
375 QList<QString> warnings;
376 log.reserve(translations.size());
377 QMutexLocker lock(&m_mutex);
378
379 const int id = m_ui->filesComboBox->currentIndex();
380 const int expectedForms = m_dataModel->model(id)->numerusForms().size();
381
382 for (const auto &[msg, translationList] : translations.asKeyValueRange()) {
383 const QString displayTranslation = translationList.size() == 1
384 ? translationList.first().simplified()
385 : translationList.join(" | "_L1);
386 log.append({ msg->sourceText().simplified(), displayTranslation });
387 if (msg->isPlural() && translationList.size() != expectedForms)
388 warnings.append(tr("Plural count expected %1, got %2 for \"%3\".")
389 .arg(expectedForms)
390 .arg(translationList.size())
391 .arg(msg->sourceText()));
392 m_receivedTranslations.append(
393 std::make_pair(m_ongoingTranslations.take(msg), translationList));
394 }
395 logInfo(tr("Translation Batch:"));
396 logProgress(log);
397 for (const QString &warning : std::as_const(warnings))
398 logWarning(warning);
399}
400
401void MachineTranslationDialog::onNewDebugMessage(const QByteArray &message, bool fromLlm)
402{
403 const QString color = isDarkMode() ? "yellow"_L1 : "orange"_L1;
404 const QString from = fromLlm ? "LLM:"_L1 : "Qt Linguist:"_L1;
405 const QString log =
406 "<p style=\"color:red; font-weight:bold; margin:0;\">%1</p>"
407 "<p style=\"color:%2; font-weight:normal; font-size:small; margin:0;\">%3</p>"
408 "<hr/>"_L1.arg(from, color, QString::fromUtf8(message).toHtmlEscaped());
409 m_ui->translationLog->append(log);
410}
411
412void MachineTranslationDialog::onFilterChanged(int id)
413{
414 m_ui->groupLabel->setEnabled(id != 0);
415 m_ui->groupListWidget->setEnabled(id != 0);
416 m_ui->groupListWidget->clear();
417 int modelId = m_ui->filesComboBox->currentIndex();
418 if (modelId < 0)
419 return;
420
421 QList<QPair<QString, int>> groupsWithIndices;
422 if (id == 1) {
423 for (int i = 0; i < m_dataModel->model(modelId)->contextCount(); i++)
424 groupsWithIndices.append(
425 { m_dataModel->model(modelId)->groupItem(i, TEXTBASED)->group(), i });
426 } else if (id == 2) {
427 for (int i = 0; i < m_dataModel->model(modelId)->labelCount(); i++)
428 groupsWithIndices.append(
429 { m_dataModel->model(modelId)->groupItem(i, IDBASED)->group(), i });
430 }
431
432 std::sort(groupsWithIndices.begin(), groupsWithIndices.end(),
433 [](const QPair<QString, int> &a, const QPair<QString, int> &b) {
434 return a.first.compare(b.first, Qt::CaseInsensitive) < 0;
435 });
436
437 for (const auto &group : groupsWithIndices) {
438 QListWidgetItem *item = new QListWidgetItem(group.first);
439 item->setData(Qt::UserRole, group.second);
440 m_ui->groupListWidget->addItem(item);
441 }
442}
443
444void MachineTranslationDialog::applyTranslations()
445{
446 QMutexLocker lock(&m_mutex);
447 for (const auto &[item, translations] : std::as_const(m_receivedTranslations))
448 m_dataModel->setTranslations(item, translations);
449 refresh(false);
450 logInfo(tr("Translations Applied."));
451 updateStatus();
452}
453
454void MachineTranslationDialog::onTranslationFailed(QList<const TranslatorMessage *> failed)
455{
456 QList<QStringList> log;
457 log.reserve(failed.size() + 1);
458
459 QMutexLocker lock(&m_mutex);
460 m_failedTranslations += failed.size();
461 for (const TranslatorMessage *m : std::as_const(failed)) {
462 log << QStringList{ m->sourceText().simplified() };
463 m_ongoingTranslations.remove(m);
464 }
465 logError(tr("Failed Translation(s):"));
466 logProgress(log);
467}
468
469void MachineTranslationDialog::updateStatus()
470{
471 const int model = m_ui->filesComboBox->currentIndex();
472 const int filter = m_ui->filterComboBox->currentIndex();
473
474 QList<QListWidgetItem *> selectedItems;
475 if (filter > 0)
476 selectedItems = m_ui->groupListWidget->selectedItems();
477
478 if (model < 0 || filter < 0 || (filter > 0 && selectedItems.isEmpty())) {
479 //: No selected items
480 m_ui->selectionLabel->setText(tr("Selection status: -"));
481 } else if (filter == 0) {
482 int count = 0;
483 for (DataModelIterator it(IDBASED, m_dataModel->model(model)); it.isValid(); ++it)
484 if (it.current()->translation().isEmpty()
485 && !it.current()->message().sourceText().isEmpty())
486 count++;
487 for (DataModelIterator it(TEXTBASED, m_dataModel->model(model)); it.isValid(); ++it)
488 if (it.current()->translation().isEmpty()
489 && !it.current()->message().sourceText().isEmpty())
490 count++;
491
492 m_ui->selectionLabel->setText(tr("Selected %n item(s).", 0, count));
493 } else if (!selectedItems.isEmpty()) {
494 const auto type = (filter == 1) ? TEXTBASED : IDBASED;
495 int count = 0;
496 for (QListWidgetItem *item : std::as_const(selectedItems)) {
497 const int groupIdx = item->data(Qt::UserRole).toInt();
498 const GroupItem *g = m_dataModel->model(model)->groupItem(groupIdx, type);
499 for (int i = 0; i < g->messageCount(); i++)
500 if (g->messageItem(i)->message().translation().isEmpty()
501 && !g->messageItem(i)->message().sourceText().isEmpty())
502 count++;
503 }
504 m_ui->selectionLabel->setText(
505 tr("Selected %n item(s) in %1 group(s).", 0, count).arg(selectedItems.size()));
506 }
507}
508
509void MachineTranslationDialog::connectToServer()
510{
511 if (m_ui->serverText->text().isEmpty()) {
512 setConnectionState(ConnectionState::NotConnected);
513 return;
514 }
515 setConnectionState(ConnectionState::Connecting);
516 m_translator->setUrl(m_ui->serverText->text());
517 m_translator->setApiKey(m_ui->apiKeyEdit->text());
518 m_translator->requestModels();
519}
520
521void MachineTranslationDialog::onApiTypeChanged(int index)
522{
523 const QVariant data = m_ui->apiTypeComboBox->itemData(index);
524 if (!data.isValid())
525 return;
526
527 const auto apiType = static_cast<TranslationApiType>(data.toInt());
528 const ApiTypeInfo *info = findApiTypeInfo(apiType);
529 if (!info)
530 return;
531
532 QSettings config;
533 config.setValue(settingPath(selectedApiTypeSettingsKey), static_cast<int>(apiType));
534
535 m_translator->setApiType(apiType);
536 m_ui->serverText->setText(QString::fromLatin1(info->defaultUrl));
537 m_ui->modelComboBox->clear();
538
539 // Show/hide API key field based on API type
540 m_ui->apiKeyLabel->setVisible(info->showApiKeyField);
541 m_ui->apiKeyEdit->setVisible(info->showApiKeyField);
542
543 connectToServer();
544}
545
546void MachineTranslationDialog::onServerUrlChanged()
547{
548 if (m_connectionState == ConnectionState::Connected
549 && m_ui->serverText->text() != m_lastConnectedUrl) {
550 setConnectionState(ConnectionState::Modified);
551 }
552}
553
554void MachineTranslationDialog::onModelsReceived(const QStringList &models)
555{
556 if (models.isEmpty()) {
557 setConnectionState(ConnectionState::Failed);
558 } else {
559 m_lastConnectedUrl = m_ui->serverText->text();
560 setConnectionState(ConnectionState::Connected);
561
562 QSettings config;
563 QString savedModel = config.value(settingPath(selectedModelSettingsKey)).toString();
564 m_ui->modelComboBox->clear();
565 m_ui->modelComboBox->addItems(models);
566
567 // Restore saved selection if found
568 if (!savedModel.isEmpty()) {
569 int index = m_ui->modelComboBox->findText(savedModel);
570 if (index >= 0)
571 m_ui->modelComboBox->setCurrentIndex(index);
572 }
573 }
574}
575
576void MachineTranslationDialog::setConnectionState(ConnectionState state)
577{
578 m_connectionState = state;
579 updateConnectionIndicator();
580}
581
582void MachineTranslationDialog::updateConnectionIndicator()
583{
584 QString statusText;
585 QString styleSheet;
586
587 switch (m_connectionState) {
588 case ConnectionState::NotConnected:
589 statusText = tr("Not connected - click \"Connect\" to fetch models");
590 styleSheet = "QLabel { color: gray; }"_L1;
591 break;
592 case ConnectionState::Connecting:
593 statusText = tr("Connecting...");
594 styleSheet = "QLabel { color: orange; }"_L1;
595 break;
596 case ConnectionState::Connected:
597 statusText = tr("Connected");
598 styleSheet = "QLabel { color: green;}"_L1;
599 break;
600 case ConnectionState::Failed:
601 statusText = tr("Connection failed - verify server URL and click \"Connect\"");
602 styleSheet = "QLabel { color: red; }"_L1;
603 break;
604 case ConnectionState::Modified:
605 statusText = tr("URL modified - click \"Connect\" to apply");
606 styleSheet = "QLabel { color: orange; }"_L1;
607 break;
608 }
609
610 m_ui->connectionStatusLabel->setText(statusText);
611 m_ui->connectionStatusLabel->setStyleSheet(styleSheet);
612}
613
614void MachineTranslationDialog::toggleAdvancedSettings(bool checked)
615{
616 m_ui->advancedSettingsWidget->setVisible(checked);
617 m_ui->advancedSettingsToggle->setText(tr("Advanced Settings") + (checked ? " -"_L1 : " +"_L1));
618 if (checked) {
619 loadAdvancedSettings();
620 validateAdvancedSettings();
621 }
622}
623
624void MachineTranslationDialog::loadAdvancedSettings()
625{
626 m_ui->maxRetriesSpinBox->setValue(TranslationSettings::maxRetries());
627 m_ui->maxConcurrentBatchesSpinBox->setValue(TranslationSettings::maxConcurrentBatches());
628 m_ui->transferTimeoutSpinBox->setValue(TranslationSettings::transferTimeoutMs() / 1000);
629 m_ui->maxBatchSizeSpinBox->setValue(TranslationSettings::maxBatchSize());
630 m_ui->temperatureSpinBox->setValue(TranslationSettings::temperature());
631 m_ui->maxJsonFormatTriesSpinBox->setValue(TranslationSettings::maxJsonFormatTries());
632 m_ui->ollamaWakeUpTimeoutSpinBox->setValue(TranslationSettings::ollamaWakeUpTimeoutMs() / 1000);
633}
634
635void MachineTranslationDialog::saveAdvancedSettings()
636{
637 TranslationSettings::setMaxRetries(m_ui->maxRetriesSpinBox->value());
638 TranslationSettings::setMaxConcurrentBatches(m_ui->maxConcurrentBatchesSpinBox->value());
639 TranslationSettings::setTransferTimeoutMs(m_ui->transferTimeoutSpinBox->value() * 1000);
640 TranslationSettings::setMaxBatchSize(m_ui->maxBatchSizeSpinBox->value());
641 TranslationSettings::setTemperature(m_ui->temperatureSpinBox->value());
642 TranslationSettings::setMaxJsonFormatTries(m_ui->maxJsonFormatTriesSpinBox->value());
643 TranslationSettings::setOllamaWakeUpTimeoutMs(m_ui->ollamaWakeUpTimeoutSpinBox->value() * 1000);
644}
645
646void MachineTranslationDialog::resetAdvancedSettings()
647{
649 loadAdvancedSettings();
650 validateAdvancedSettings();
651}
652
653void MachineTranslationDialog::validateAdvancedSettings()
654{
655 const int maxRetries = m_ui->maxRetriesSpinBox->value();
656 const int maxJsonFormatTries = m_ui->maxJsonFormatTriesSpinBox->value();
657
658 QStringList warnings;
659
660 // Warn if JSON format tries is too low
661 if (maxJsonFormatTries < 3) {
662 //: "Maximum JSON Format Tries" should use the same translation as
663 //: the label "Maximum JSON Format Tries" in Advanced Settings
664 warnings << tr("Warning: Maximum JSON Format Tries: Low value may cause unnecessary "
665 "format switching due to temporary errors. Recommended: 3 or higher.");
666 }
667
668 // Warn if max retries doesn't cover all format stages
669 // 3 format stages: JsonObject, JsonSchema, None
670 if (maxRetries < maxJsonFormatTries * 3) {
671 //: "Maximum Retries" and "Maximum JSON Format Tries" should use the same
672 //: translations as the labels "Maximum Retries" and "Maximum JSON Format Tries"
673 //: in Advanced Settings
674 warnings << tr("Warning: Maximum Retries: Should be at least 3x 'Maximum JSON "
675 "Format Tries' for full fallback coverage");
676 }
677
678 m_ui->settingsWarningLabel->setText(warnings.join(u'\n'));
679}
680
682
683QT_END_NAMESPACE
bool isValid() const
DataModelIterator(TranslationType type, const DataModel *model=0, int groupNo=0, int messageNo=0)
MessageItem * current() const
GroupItem * groupItem(int index, TranslationType type) const
int labelCount() const
int contextCount() const
void setDataModel(MultiDataModel *dm)
void stop() noexcept
const TranslatorMessage & message() const
DataModel * model(int i)
static void setMaxJsonFormatTries(int value)
static void setMaxBatchSize(int value)
static void setTemperature(double value)
static void setMaxConcurrentBatches(int value)
static void setTransferTimeoutMs(int value)
static void setMaxRetries(int value)
static void setOllamaWakeUpTimeoutMs(int value)
TranslationApiType
@ IDBASED
@ TEXTBASED