10using namespace Qt::Literals::StringLiterals;
13static std::optional<QJsonArray> recursiveFind(
const QJsonValue &jval,
const QString &key)
15 if (jval.isObject()) {
16 const QJsonObject obj = jval.toObject();
17 auto it = obj.find(key);
18 if (it != obj.end() && it->isArray())
20 for (it = obj.constBegin(); it != obj.constEnd(); ++it) {
21 if (it.key().trimmed() == key && it.value().isArray())
22 return it.value().toArray();
23 if (
const auto r = recursiveFind(it.value(), key); r)
26 }
else if (jval.isArray()) {
27 const QJsonArray arr = jval.toArray();
28 for (
const QJsonValue &element : arr)
29 if (
const auto r = recursiveFind(element, key); r)
31 }
else if (jval.isString()) {
32 QString str = jval.toString();
33 const int startIdx = str.indexOf(
'{'_L1);
34 const int endIdx = str.lastIndexOf(
'}'_L1);
35 if (startIdx < 0 || endIdx < 0)
37 str.slice(startIdx, endIdx - startIdx + 1);
39 auto inner = QJsonDocument::fromJson(str.toUtf8(), &err);
40 if (err.error != QJsonParseError::NoError || !inner.isObject())
42 const auto obj = inner.object();
43 if (
auto it = obj.find(key); it != obj.end()) {
44 if (it.value().isArray())
45 return it.value().toArray();
55 : m_payloadBase(std::make_unique<QJsonObject>()),
56 m_systemMessage(std::make_unique<QJsonObject>())
58 m_payloadBase->insert(
"stream"_L1,
false);
59 m_payloadBase->insert(
"think"_L1,
false);
62 opts.insert(
"temperature"_L1, 0.05);
63 m_payloadBase->insert(
"options"_L1, opts);
65 m_systemMessage->insert(
"role"_L1,
"system"_L1);
66 m_systemMessage->insert(
"content"_L1, makeSystemPrompt());
73 QHash<QString, QList<
const TranslatorMessage *>> groups;
75 for (
const auto &item : messages.items)
76 groups[item->context() + item->label()].append(item);
79 out.reserve(groups.size());
80 for (
auto it = groups.cbegin(); it != groups.cend(); ++it) {
81 auto msgIt = it.value().cbegin();
82 while (msgIt != it.value().cend()) {
84 b.srcLang = messages.srcLang;
85 b.tgtLang = messages.tgtLang;
87 b.userContext = userContext;
88 b.items.reserve(it.value().size());
89 while (msgIt != it.value().cend() && b.items.size() < s_maxBatchSize) {
92 item.translation = item
.msg->translation();
93 b.items.append(std::move(item));
96 out.append(std::move(b));
105 QJsonDocument doc = QJsonDocument::fromJson(response, &err);
106 if (err.error != QJsonParseError::NoError) {
107 m_useJsonFormat =
false;
111 auto translations = recursiveFind(doc.object(),
"Translations"_L1);
112 QHash<QString, QString> out;
114 m_useJsonFormat =
false;
118 out.reserve(translations->size());
119 for (
const QJsonValue &v : std::as_const(*translations)) {
121 const QJsonObject obj = v.toObject();
122 const QString key = obj.keys().first();
123 if (QJsonValue val = obj.value(key); val.isString())
124 out[key] = val.toString();
133 QJsonDocument doc = QJsonDocument::fromJson(response, &err);
134 if (err.error != QJsonParseError::NoError)
136 const QJsonObject obj = doc.object();
137 const QJsonArray arr = obj.value(
"models"_L1).toArray();
139 for (
const QJsonValue &v : arr)
140 models.append(v.toObject().value(
"name"_L1).toString());
146 QJsonObject userMessage;
147 userMessage.insert(
"role"_L1,
"user"_L1);
148 userMessage.insert(
"content"_L1, makePrompt(b));
151 messages.append(*m_systemMessage);
152 messages.append(userMessage);
154 QJsonObject req = *m_payloadBase;
155 req.insert(
"messages"_L1, messages);
158 req.insert(
"format"_L1,
"json"_L1);
160 return QJsonDocument(req).toJson();
165 if (
auto m = m_payloadBase->constFind(
"model"_L1);
166 m == m_payloadBase->constEnd() || *m != modelName) {
167 m_useJsonFormat =
true;
168 m_payloadBase->insert(
"model"_L1, modelName);
171 std::optional<QByteArray> res;
172 if (!m_lastWakeupTimer.isValid() || m_lastWakeupTimer.hasExpired(s_wakeUpTimeOut)) {
173 m_lastWakeupTimer.start();
175 wakeup.insert(
"model"_L1, modelName);
176 res.emplace(QJsonDocument(wakeup).toJson());
189 return QUrl(m_url).resolved(QUrl(
"/api/chat"_L1));
194 return QUrl(m_url).resolved(QUrl(
"/api/tags"_L1));
200 lines.reserve(b.items.size() + 32);
202 if (!b.userContext.isEmpty())
203 lines <<
"Application Context: "_L1 + b.userContext;
205 lines <<
"Context: "_L1 + b.context;
206 lines <<
"Target: "_L1 + b.tgtLang;
207 lines <<
"Items:"_L1;
208 for (
const Item &it : b.items) {
209 QString line =
"- source: '%1'"_L1.arg(it.msg->sourceText());
210 if (
const QString comment = it.msg->comment(); !comment.isEmpty())
211 line +=
", comment: '%1'"_L1.arg(comment);
215 return lines.join(QLatin1Char(
'\n'));
218QString
Ollama::makeSystemPrompt()
const
220 static QString systemPrompt = uR"(
221You are a professional software translator specialized in Qt UI strings.
222
223When given a list of items of the given 'Context', each may include:
224- source: the original text to translate
225- comment: an optional developer note for more context
226
227If "Application Context" is provided, use it to understand the domain and terminology
228appropriate for the application (e.g., medical, financial, gaming) to produce more
229accurate and contextually appropriate translations.
230
231Translate the items into the **target language** specified by the user,
232preserving keyboard accelerators (e.g. "&File") and placeholders (e.g. "%1").
233
234RESULT FORMAT (MUST FOLLOW):
235A single JSON object with one key, "Translations",
236whose value is an array of objects.
237Each object maps the original source string to translated string:
238
239Two examples:
240
241Input:
242Context: MainWindow
243Target: German
244Items:
245 - source: "File"
246 - source: "Exit"
247 - source: "&Open", comment: "opens a document"
248
249Output:
250{"Translations":[{"File":"Datei"},{"Exit":"Beenden"},{"&Open":"&Öffnen"}]}
251
252Input:
253Context: MainWindow
254Target: French
255Items:
256– source: "File"
257– source: "Exit"
258Output:
259{"Translations":[{"File":"Fichier"},{"Exit":"Quitter"}]}
260
261Return **only** valid JSON, no code fences, no extra text.
262After generating and before returning, verify:
2631. Every string is in the target language; if any aren’t, correct them before returning.
2642. Every JSON key exactly matches one of the input source strings.
2653. No key equals its value.
2664. Every string is translated
267)"_s;
const TranslatorMessage * msg
QList< Batch > makeBatches(const Messages &messages, const QString &userContext) const override
QUrl translationEndpoint() const override
QUrl discoveryEndpoint() const override
QStringList extractModels(const QByteArray &data) const override
QHash< QString, QString > extractTranslations(const QByteArray &response) override
QByteArray payload(const Batch &b) const override
std::optional< QByteArray > stageModel(const QString &modelName) override
void setUrl(const QString &url) override