Add system prompt, glossary, presets for Ollama/WebLLM, image translation support
This commit is contained in:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user