mtengines
Version:
Machine Translation (MT) library written in TypeScript
224 lines • 8.99 kB
JavaScript
/*******************************************************************************
* Copyright (c) 2023-2026 Maxprograms.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse License 1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-v10.html
*
* Contributors:
* Maxprograms - initial API and implementation
*******************************************************************************/
import { MTMatch } from './MTMatch.js';
import { MTUtils } from './MTUtils.js';
const CONNECT_TIMEOUT_MS = 5000;
const CHAT_TIMEOUT_MS = 300000;
export class OllamaTranslator {
baseUrl;
model;
think = false;
srcLang = '';
tgtLang = '';
constructor(baseUrl, model, think) {
this.baseUrl = baseUrl;
this.model = model;
if (think !== undefined) {
this.think = think;
}
}
setThink(think) {
this.think = think;
}
fetchWithTimeout(url, options, timeoutMs) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
return fetch(url, { ...options, signal: controller.signal })
.finally(() => clearTimeout(timer));
}
chat(messages) {
return new Promise((resolve, reject) => {
this.fetchWithTimeout(this.baseUrl + '/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: this.model,
messages: messages,
stream: false,
think: this.think
})
}, CHAT_TIMEOUT_MS).then((response) => {
if (!response.ok) {
throw new Error('Ollama request failed: ' + response.status + ' ' + response.statusText);
}
return response.json();
}).then((data) => {
resolve(data.message.content);
}).catch((error) => {
if (error.name === 'AbortError') {
reject(new Error('Request timed out after ' + (CHAT_TIMEOUT_MS / 1000) + 's.'));
return;
}
reject(error);
});
});
}
getMTMatch(source, terms) {
let prompt = MTUtils.generatePrompt(source, this.srcLang, this.tgtLang, terms);
let messages = [
{ role: 'system', content: MTUtils.getRole(this.srcLang, this.tgtLang) },
{ role: 'user', content: prompt }
];
return new Promise((resolve, reject) => {
this.chat(messages).then((translation) => {
if (translation.startsWith('```xml') && translation.endsWith('```')) {
translation = translation.substring(6, translation.length - 3).trim();
}
if (translation.startsWith('```') && translation.endsWith('```')) {
translation = translation.substring(3, translation.length - 3).trim();
}
if (!translation.trim().startsWith('<target') && !translation.trim().endsWith('</target>')) {
translation = '<target>' + translation + '</target>';
}
let target = MTUtils.toXMLElement(translation);
let space = source.getAttribute('xml:space');
if (space) {
target.setAttribute(space);
}
resolve(new MTMatch(source, target, this.getShortName()));
}).catch((error) => {
reject(error);
});
});
}
handlesTags() {
return true;
}
fixesMatches() {
return true;
}
fixMatch(originalSource, matchSource, matchTarget) {
let prompt = MTUtils.fixMatchPrompt(originalSource, matchSource, matchTarget);
let messages = [
{ role: 'system', content: MTUtils.getRole(this.srcLang, this.tgtLang) },
{ role: 'user', content: prompt }
];
return new Promise((resolve, reject) => {
this.chat(messages).then((translation) => {
if (translation.startsWith('\n\n')) {
translation = translation.substring(2);
}
while (translation.startsWith('"') && translation.endsWith('"')) {
translation = translation.substring(1, translation.length - 1);
}
if (translation.startsWith('```xml') && translation.endsWith('```')) {
translation = translation.substring(6, translation.length - 3).trim();
}
if (!translation.trim().startsWith('<target') && !translation.trim().endsWith('</target>')) {
translation = '<target>' + translation + '</target>';
}
let target = MTUtils.toXMLElement(translation);
resolve(new MTMatch(originalSource, target, this.getShortName()));
}).catch((error) => {
reject(error);
});
});
}
fixesTags() {
return true;
}
fixTags(source, target) {
let prompt = MTUtils.fixTagsPrompt(source, target, this.srcLang, this.tgtLang);
let messages = [
{ role: 'system', content: MTUtils.getRole(this.srcLang, this.tgtLang) },
{ role: 'user', content: prompt }
];
return new Promise((resolve, reject) => {
this.chat(messages).then((translation) => {
if (translation.startsWith('\n\n')) {
translation = translation.substring(2);
}
while (translation.startsWith('"') && translation.endsWith('"')) {
translation = translation.substring(1, translation.length - 1);
}
if (translation.startsWith('```xml') && translation.endsWith('```')) {
translation = translation.substring(6, translation.length - 3).trim();
}
if (!translation.trim().startsWith('<target') && !translation.trim().endsWith('</target>')) {
translation = '<target>' + translation + '</target>';
}
let element = MTUtils.toXMLElement(translation);
resolve(element);
}).catch((error) => {
reject(error);
});
});
}
getName() {
return 'Ollama';
}
getShortName() {
return 'Ollama';
}
getSourceLanguages() {
return MTUtils.getLanguages();
}
getTargetLanguages() {
return MTUtils.getLanguages();
}
setSourceLanguage(lang) {
this.srcLang = lang;
}
getSourceLanguage() {
return this.srcLang;
}
setTargetLanguage(lang) {
this.tgtLang = lang;
}
getTargetLanguage() {
return this.tgtLang;
}
translate(source) {
if (this.srcLang === '' || this.tgtLang === '') {
return Promise.reject(new Error('Source and Target languages must be set before translation.'));
}
let prompt = MTUtils.translatePropmt(source, this.srcLang, this.tgtLang);
let messages = [
{ role: 'system', content: MTUtils.getRole(this.srcLang, this.tgtLang) },
{ role: 'user', content: prompt }
];
return new Promise((resolve, reject) => {
this.chat(messages).then((translation) => {
if (translation.startsWith('\n\n')) {
translation = translation.substring(2);
}
while (translation.startsWith('"') && translation.endsWith('"')) {
translation = translation.substring(1, translation.length - 1);
}
if (source.startsWith('"') && source.endsWith('"')) {
translation = '"' + translation + '"';
}
resolve(translation);
}).catch((error) => {
reject(error);
});
});
}
async getAvailableModels() {
let response;
try {
response = await this.fetchWithTimeout(this.baseUrl + '/api/tags', {}, CONNECT_TIMEOUT_MS);
}
catch (err) {
if (err.name === 'AbortError') {
throw new Error('Ollama did not respond within ' + (CONNECT_TIMEOUT_MS / 1000) + 's. Is it running?');
}
throw new Error('Cannot reach Ollama at ' + this.baseUrl + '. Is it running?\n ' + err.message);
}
if (!response.ok) {
throw new Error('Ollama returned status ' + response.status);
}
const data = await response.json();
return data.models.map((m) => [m.name, m.name]);
}
}
//# sourceMappingURL=OllamaTranslator.js.map