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) {
111 auto translations = recursiveFind(doc.object(),
"Translations"_L1);
112 QHash<QString, QString> out;
122 if (m_useJsonFormat > 0)
123 m_useJsonFormat = std::numeric_limits<
int>::max();
125 out.reserve(translations->size());
126 for (
const QJsonValue &v : std::as_const(*translations)) {
128 const QJsonObject obj = v.toObject();
129 const QString key = obj.keys().first();
130 if (QJsonValue val = obj.value(key); val.isString())
131 out[key] = val.toString();
140 QJsonDocument doc = QJsonDocument::fromJson(response, &err);
141 if (err.error != QJsonParseError::NoError)
143 const QJsonObject obj = doc.object();
144 const QJsonArray arr = obj.value(
"models"_L1).toArray();
146 for (
const QJsonValue &v : arr)
147 models.append(v.toObject().value(
"name"_L1).toString());
153 QJsonObject userMessage;
154 userMessage.insert(
"role"_L1,
"user"_L1);
155 userMessage.insert(
"content"_L1, makePrompt(b));
158 messages.append(*m_systemMessage);
159 messages.append(userMessage);
161 QJsonObject req = *m_payloadBase;
162 req.insert(
"messages"_L1, messages);
164 if (m_useJsonFormat > 0)
165 req.insert(
"format"_L1,
"json"_L1);
167 return QJsonDocument(req).toJson();
172 if (
auto m = m_payloadBase->constFind(
"model"_L1);
173 m == m_payloadBase->constEnd() || *m != modelName) {
174 m_useJsonFormat = s_maxJsonFormatTry;
175 m_payloadBase->insert(
"model"_L1, modelName);
178 std::optional<QByteArray> res;
179 if (!m_lastWakeupTimer.isValid() || m_lastWakeupTimer.hasExpired(s_wakeUpTimeOut)) {
180 m_lastWakeupTimer.start();
182 wakeup.insert(
"model"_L1, modelName);
183 res.emplace(QJsonDocument(wakeup).toJson());
196 return QUrl(m_url).resolved(QUrl(
"/api/chat"_L1));
201 return QUrl(m_url).resolved(QUrl(
"/api/tags"_L1));
207 lines.reserve(b.items.size() + 32);
209 if (!b.userContext.isEmpty())
210 lines <<
"Application Context: "_L1 + b.userContext;
212 lines <<
"Context: "_L1 + b.context;
213 lines <<
"Target: "_L1 + b.tgtLang;
214 lines <<
"Items:"_L1;
215 for (
const Item &it : b.items) {
216 QString line =
"- source: '%1'"_L1.arg(it.msg->sourceText());
217 if (
const QString comment = it.msg->comment(); !comment.isEmpty())
218 line +=
", comment: '%1'"_L1.arg(comment);
222 return lines.join(QLatin1Char(
'\n'));
225QString
Ollama::makeSystemPrompt()
const
227 static QString systemPrompt = uR"(
228You are a professional software translator specialized in Qt UI strings.
229
230When given a list of items of the given 'Context', each may include:
231- source: the original text to translate
232- comment: an optional developer note for more context
233
234If "Application Context" is provided, use it to understand the domain and terminology
235appropriate for the application (e.g., medical, financial, gaming) to produce more
236accurate and contextually appropriate translations.
237
238Translate the items into the **target language** specified by the user,
239preserving keyboard accelerators (e.g. "&File"), placeholders (e.g. "%1"),
240and ending punctuation.
241
242RESULT FORMAT (MUST FOLLOW):
243A single JSON object with one key, "Translations",
244whose value is an array of objects.
245Each object maps the original source string to translated string:
246
247Two examples:
248
249Input:
250Context: MainWindow
251Target: German
252Items:
253 - source: "File"
254 - source: "Exit"
255 - source: "&Open", comment: "opens a document"
256
257Output:
258{"Translations":[{"File":"Datei"},{"Exit":"Beenden"},{"&Open":"&Öffnen"}]}
259
260Input:
261Context: MainWindow
262Target: French
263Items:
264– source: "File"
265– source: "Exit"
266Output:
267{"Translations":[{"File":"Fichier"},{"Exit":"Quitter"}]}
268
269Return **only** valid JSON, no code fences, no extra text.
270After generating and before returning, verify:
2711. Every string is in the target language; if any aren't, correct them before returning.
2722. Every JSON key exactly matches one of the input source strings.
2733. No key equals its value.
2744. Every string is translated
275)"_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