UNPKG

mtengines

Version:

Machine Translation (MT) library written in TypeScript

224 lines 8.99 kB
/******************************************************************************* * 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