Add system prompt, glossary, presets for Ollama/WebLLM, image translation support

This commit is contained in:
2025-11-30 16:45:41 +01:00
parent 465cab8a61
commit e48ea07e44
6 changed files with 497 additions and 51 deletions

View File

@@ -309,7 +309,7 @@
</div>
<div class="form-group">
<label for="ollama-model">Modèle Ollama</label>
<input type="text" id="ollama-model" value="llama3" placeholder="llama3, mistral, etc.">
<input type="text" id="ollama-model" value="llama3.2" placeholder="llama3.2, mistral, etc.">
</div>
</div>
<button onclick="listOllamaModels()" class="btn-secondary">List Available Models</button>
@@ -318,6 +318,39 @@
<div id="models-result"></div>
</div>
<!-- System Prompt for LLM Translation -->
<div class="card">
<h2>Translation Context (Ollama / WebLLM)</h2>
<p style="font-size: 13px; color: #718096; margin-bottom: 15px;">
Provide context, technical glossary, or specific instructions to improve translation quality.
</p>
<div class="form-group">
<label for="system-prompt">System Prompt / Instructions</label>
<textarea id="system-prompt" rows="4" style="width: 100%; padding: 10px 14px; border: 1px solid #cbd5e0; border-radius: 6px; font-size: 14px; font-family: inherit; resize: vertical;" placeholder="Example: You are translating HVAC technical documents. Use these terms:
- Batterie (FR) = Coil (EN)
- Groupe froid (FR) = Chiller (EN)
- CTA (FR) = AHU (EN)"></textarea>
</div>
<div class="form-group">
<label for="glossary">Technical Glossary (one per line: source=target)</label>
<textarea id="glossary" rows="5" style="width: 100%; padding: 10px 14px; border: 1px solid #cbd5e0; border-radius: 6px; font-size: 13px; font-family: monospace; resize: vertical;" placeholder="batterie=coil
groupe froid=chiller
CTA=AHU
échangeur=heat exchanger
vanne 3 voies=3-way valve"></textarea>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button onclick="loadPreset('hvac')" class="btn-secondary" style="font-size: 12px;">HVAC Preset</button>
<button onclick="loadPreset('it')" class="btn-secondary" style="font-size: 12px;">IT Preset</button>
<button onclick="loadPreset('legal')" class="btn-secondary" style="font-size: 12px;">Legal Preset</button>
<button onclick="loadPreset('medical')" class="btn-secondary" style="font-size: 12px;">Medical Preset</button>
<button onclick="clearPrompt()" class="btn-secondary" style="font-size: 12px; background: #dc2626;">Clear</button>
</div>
</div>
<!-- Traduction de fichier -->
<div class="card">
<h2>Document Translation</h2>
@@ -335,6 +368,8 @@
<div class="form-group">
<label for="target-lang">Target Language</label>
<select id="target-lang">
<option value="en">English (en)</option>
<option value="fa">Persian / Farsi (fa)</option>
<option value="es">Espagnol (es)</option>
<option value="fr">Français (fr)</option>
<option value="de">Allemand (de)</option>
@@ -350,10 +385,10 @@
<div class="form-group">
<label for="provider">Translation Service</label>
<select id="provider" onchange="toggleImageTranslation()">
<select id="provider" onchange="toggleProviderOptions()">
<option value="google">Google Translate (Default)</option>
<option value="ollama">Ollama LLM (Local Server)</option>
<option value="webllm">WebLLM (Browser - No Server)</option>
<option value="webllm">WebLLM (Browser - WebGPU)</option>
<option value="deepl">DeepL</option>
<option value="libre">LibreTranslate</option>
</select>
@@ -363,11 +398,11 @@
<div class="form-group" id="image-translation-option" style="display: none;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="translate-images" style="width: auto; margin-right: 10px;">
<span>Translate images with Ollama Vision (requires llava model)</span>
<span>Translate images with vision (use multimodal models: gemma3, qwen3-vl, llava, etc.)</span>
</label>
</div>
<div class="form-group" id="webllm-info" style="display: none; padding: 12px; background: #e0f2ff; border-radius: 6px; border-left: 4px solid #2563eb;">
<div class="form-group" id="webllm-options" style="display: none; padding: 12px; background: #e0f2ff; border-radius: 6px; border-left: 4px solid #2563eb;">
<p style="margin: 0 0 10px 0; font-size: 13px; color: #1e40af;">
<strong>WebLLM Mode:</strong> Translation runs entirely in your browser using WebGPU. First use downloads the model.
</p>
@@ -375,8 +410,8 @@
<div>
<label for="webllm-model" style="font-size: 12px; color: #4a5568; margin-bottom: 4px;">Select Model:</label>
<select id="webllm-model" style="width: 100%; padding: 6px; font-size: 13px; border: 1px solid #cbd5e0; border-radius: 4px;">
<option value="Llama-3.2-3B-Instruct-q4f32_1-MLC">Llama 3.2 3B (~2GB) - Recommended</option>
<option value="Llama-3.1-8B-Instruct-q4f32_1-MLC">Llama 3.1 8B (~4.5GB)</option>
<option value="Llama-3.2-3B-Instruct-q4f32_1-MLC">Llama 3.2 3B (~2GB)</option>
<option value="Phi-3.5-mini-instruct-q4f16_1-MLC">Phi 3.5 Mini (~2.5GB)</option>
<option value="Mistral-7B-Instruct-v0.3-q4f16_1-MLC">Mistral 7B (~4.5GB)</option>
<option value="gemma-2-2b-it-q4f16_1-MLC">Gemma 2 2B (~1.5GB)</option>
@@ -386,6 +421,7 @@
Clear Cache
</button>
</div>
<div id="webllm-status" style="margin-top: 10px; font-size: 12px; color: #4a5568;"></div>
</div>
<button onclick="translateFile()">Translate Document</button>
@@ -445,26 +481,193 @@
}
}
// Toggle image translation option based on provider
function toggleImageTranslation() {
const provider = document.getElementById('provider').value;
const imageOption = document.getElementById('image-translation-option');
const webllmInfo = document.getElementById('webllm-info');
if (provider === 'ollama') {
imageOption.style.display = 'block';
webllmInfo.style.display = 'none';
} else if (provider === 'webllm') {
imageOption.style.display = 'none';
webllmInfo.style.display = 'block';
document.getElementById('translate-images').checked = false;
} else {
imageOption.style.display = 'none';
webllmInfo.style.display = 'none';
document.getElementById('translate-images').checked = false;
// Toggle provider options based on selection
// Preset templates for different domains
const presets = {
hvac: {
prompt: `You are translating HVAC (Heating, Ventilation, Air Conditioning) technical documents.
Use precise technical terminology. Maintain consistency with industry standards.
Keep unit measurements (kW, m³/h, Pa) unchanged.
Translate component names according to the glossary provided.`,
glossary: `batterie=coil
groupe froid=chiller
CTA=AHU (Air Handling Unit)
échangeur=heat exchanger
vanne 3 voies=3-way valve
détendeur=expansion valve
compresseur=compressor
évaporateur=evaporator
condenseur=condenser
fluide frigorigène=refrigerant
débit d'air=airflow
pression statique=static pressure
récupérateur=heat recovery unit
ventilo-convecteur=fan coil unit
gaine=duct
diffuseur=diffuser
registre=damper`
},
it: {
prompt: `You are translating IT and software documentation.
Keep technical terms, code snippets, and variable names unchanged.
Translate UI labels and user-facing text appropriately.
Maintain formatting markers like **bold** and \`code\`.`,
glossary: `serveur=server
base de données=database
requête=query
sauvegarde=backup
mise à jour=update
télécharger=download
téléverser=upload
mot de passe=password
identifiant=username
pare-feu=firewall
réseau=network
stockage=storage
conteneur=container
déploiement=deployment`
},
legal: {
prompt: `You are translating legal documents.
Use formal legal terminology. Be precise and unambiguous.
Maintain references to laws, articles, and clauses in their original form.
Use standard legal phrases for the target language.`,
glossary: `contrat=contract
clause=clause
partie=party
signataire=signatory
résiliation=termination
préavis=notice period
dommages et intérêts=damages
responsabilité=liability
juridiction=jurisdiction
arbitrage=arbitration
avenant=amendment
ayant droit=beneficiary`
},
medical: {
prompt: `You are translating medical and healthcare documents.
Use standard medical terminology (Latin/Greek roots when appropriate).
Keep drug names, dosages, and medical codes unchanged.
Be precise with anatomical terms and procedures.`,
glossary: `patient=patient
ordonnance=prescription
posologie=dosage
effet secondaire=side effect
contre-indication=contraindication
diagnostic=diagnosis
symptôme=symptom
traitement=treatment
chirurgie=surgery
anesthésie=anesthesia
perfusion=infusion
prélèvement=sample collection`
}
};
function loadPreset(presetName) {
const preset = presets[presetName];
if (preset) {
document.getElementById('system-prompt').value = preset.prompt;
document.getElementById('glossary').value = preset.glossary;
}
}
function clearPrompt() {
document.getElementById('system-prompt').value = '';
document.getElementById('glossary').value = '';
}
function getFullSystemPrompt() {
let prompt = document.getElementById('system-prompt').value || '';
const glossary = document.getElementById('glossary').value || '';
if (glossary.trim()) {
prompt += '\n\nGLOSSARY (use these exact translations):\n' + glossary;
}
return prompt;
}
function toggleProviderOptions() {
const provider = document.getElementById('provider').value;
const imageOption = document.getElementById('image-translation-option');
const webllmOptions = document.getElementById('webllm-options');
// Hide all options first
imageOption.style.display = 'none';
webllmOptions.style.display = 'none';
document.getElementById('translate-images').checked = false;
if (provider === 'ollama') {
imageOption.style.display = 'block';
} else if (provider === 'webllm') {
webllmOptions.style.display = 'block';
}
}
// WebLLM engine instance
let webllmEngine = null;
let webllmReady = false;
// Initialize WebLLM
async function initWebLLM(modelId) {
const statusDiv = document.getElementById('webllm-status');
statusDiv.innerHTML = '⏳ Loading WebLLM...';
try {
// Dynamically import WebLLM
const webllm = await import('https://esm.run/@mlc-ai/web-llm');
statusDiv.innerHTML = '⏳ Downloading model (this may take a while on first use)...';
webllmEngine = await webllm.CreateMLCEngine(modelId, {
initProgressCallback: (progress) => {
statusDiv.innerHTML = `${progress.text}`;
}
});
webllmReady = true;
statusDiv.innerHTML = '✅ Model loaded and ready!';
return true;
} catch (error) {
statusDiv.innerHTML = `❌ Error: ${error.message}`;
console.error('WebLLM init error:', error);
return false;
}
}
// Translate text with WebLLM
async function translateWithWebLLM(text, targetLang) {
if (!webllmEngine) return text;
try {
// Build system prompt with custom context and glossary
let systemPrompt = `You are a translator. Translate the user's text to ${targetLang}. Return ONLY the translation, nothing else.`;
const customPrompt = getFullSystemPrompt();
if (customPrompt.trim()) {
systemPrompt = `You are a translator. Translate the user's text to ${targetLang}. Return ONLY the translation, nothing else.
ADDITIONAL CONTEXT AND INSTRUCTIONS:
${customPrompt}`;
}
const response = await webllmEngine.chat.completions.create({
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: text }
],
temperature: 0.3,
max_tokens: 500
});
return response.choices[0].message.content.trim();
} catch (error) {
console.error('WebLLM translation error:', error);
return text;
}
}
// Liste des modèles Ollama
async function listOllamaModels() {
const url = document.getElementById('ollama-url').value;
@@ -553,11 +756,19 @@
return;
}
// Get Ollama model from configuration field (used for both text and vision)
const ollamaModel = document.getElementById('ollama-model').value || 'llama3.2';
// Get custom system prompt with glossary
const systemPrompt = getFullSystemPrompt();
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('target_language', targetLang);
formData.append('provider', provider);
formData.append('translate_images', translateImages);
formData.append('ollama_model', ollamaModel);
formData.append('system_prompt', systemPrompt);
loadingDiv.classList.add('active');
progressContainer.classList.add('active');