Files
Traduttore/renderer.js
2026-03-06 19:26:36 +01:00

336 lines
11 KiB
JavaScript

// Elementi DOM
const inputText = document.getElementById('inputText');
const outputText = document.getElementById('outputText');
const translateBtn = document.getElementById('translateBtn');
const detectedLang = document.getElementById('detectedLang');
const clearBtn = document.getElementById('clearBtn');
const copyBtn = document.getElementById('copyBtn');
const charCount = document.getElementById('charCount');
const statusIndicator = document.getElementById('statusIndicator');
const settingsBtn = document.getElementById('settingsBtn');
const settingsModal = document.getElementById('settingsModal');
const closeModal = document.getElementById('closeModal');
const baseUrlInput = document.getElementById('baseUrl');
const modelSelect = document.getElementById('modelSelect');
const refreshModels = document.getElementById('refreshModels');
const saveSettings = document.getElementById('saveSettings');
const toast = document.getElementById('toast');
// Stato applicazione
let isTranslating = false;
let currentDetectedLang = null;
// Carica impostazioni salvate
function loadSettings() {
const savedBaseUrl = localStorage.getItem('ollamaBaseUrl') || 'http://localhost:11434';
const savedModel = localStorage.getItem('ollamaModel') || '';
baseUrlInput.value = savedBaseUrl;
if (savedModel) {
const existingOption = Array.from(modelSelect.options).find(opt => opt.value === savedModel);
if (!existingOption) {
const option = document.createElement('option');
option.value = savedModel;
option.textContent = savedModel;
modelSelect.appendChild(option);
}
modelSelect.value = savedModel;
}
}
// Salva impostazioni
function saveSettingsData() {
const baseUrl = baseUrlInput.value.trim() || 'http://localhost:11434';
const model = modelSelect.value;
localStorage.setItem('ollamaBaseUrl', baseUrl);
localStorage.setItem('ollamaModel', model);
closeSettingsModal();
showToast('Impostazioni salvate!');
}
// Carica modelli disponibili
async function loadAvailableModels() {
const baseUrl = baseUrlInput.value.trim() || 'http://localhost:11434';
console.log('DEBUG - Tentativo di caricare modelli da:', baseUrl);
console.log('DEBUG - electronAPI disponibile?', !!window.electronAPI);
console.log('DEBUG - getModels funzione?', typeof window.electronAPI?.getModels);
refreshModels.disabled = true;
refreshModels.innerHTML = `
<svg class="spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"/>
<polyline points="1 20 1 14 7 14"/>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
</svg>
Caricamento...
`;
try {
const cleanBaseUrl = baseUrl.trim();
console.log('Tentativo connessione a:', cleanBaseUrl);
const result = await window.electronAPI.getModels(cleanBaseUrl);
if (result.success) {
modelSelect.innerHTML = '<option value="">Seleziona un modello...</option>';
result.models.forEach(model => {
const option = document.createElement('option');
option.value = model.name;
option.textContent = model.name;
modelSelect.appendChild(option);
});
const savedModel = localStorage.getItem('ollamaModel');
if (savedModel && result.models.find(m => m.name === savedModel)) {
modelSelect.value = savedModel;
}
showToast(`${result.models.length} modelli trovati!`);
} else {
showToast('Errore: ' + result.error, 'error');
}
} catch (error) {
console.error('Errore completo:', error);
showToast('Errore di connessione: ' + (error.message || 'Verifica che Ollama sia in esecuzione'), 'error');
} finally {
refreshModels.disabled = false;
refreshModels.innerHTML = `
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"/>
<polyline points="1 20 1 14 7 14"/>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
</svg>
Aggiorna
`;
}
}
// Rileva la lingua del testo
function detectLanguage(text) {
if (!text.trim()) {
currentDetectedLang = null;
detectedLang.textContent = 'Rilevamento automatico...';
detectedLang.className = '';
return null;
}
// Caratteri italiani comuni
const italianChars = /[àèéìòùÀÈÉÌÒÙ]/;
const italianWords = /\b(ciao|grazie|buongiorno|come|sono|per|con|su|tra|fra|nel|del|degli|della)\b/gi;
// Caratteri inglesi comuni (rari in italiano)
const englishChars = /[wkyjWQX]/;
const englishWords = /\b(the|and|for|are|with|you|that|have|this|from|they|we|say|her|she|or|an|will|my|one|all|would|there|their|what|so|up|out|if|about|who|get|which|go|me|when|make|can|like|time|no|just|him|know|take|people|into|year|your|good|some|could|them|see|other|than|then|now|look|only|come|its|over|think|also|back|after|use|two|how|our|work|first|well|way|even|new|want|because|any|these|give|day|most|us)\b/gi;
const hasItalianChars = italianChars.test(text);
const hasItalianWords = (text.match(italianWords) || []).length;
const hasEnglishChars = englishChars.test(text);
const hasEnglishWords = (text.match(englishWords) || []).length;
// Logica di rilevamento
let detected = null;
if (hasItalianChars) {
detected = 'it';
} else if (hasEnglishChars && !hasItalianWords) {
detected = 'en';
} else if (hasItalianWords > hasEnglishWords) {
detected = 'it';
} else if (hasEnglishWords > italianWords) {
detected = 'en';
} else {
// Fallback: verifica la distribuzione delle vocali
const vowels = text.toLowerCase().match(/[aeiou]/g) || [];
const yCount = (text.match(/y/gi) || []).length;
if (yCount > 0) {
detected = 'en';
} else {
detected = 'it'; // default
}
}
currentDetectedLang = detected;
updateLanguageIndicator();
return detected;
}
// Aggiorna l'indicatore di lingua
function updateLanguageIndicator() {
if (currentDetectedLang === 'it') {
detectedLang.innerHTML = '🇮🇹 Italiano → 🇬🇧 Inglese';
detectedLang.className = 'detected-it';
} else if (currentDetectedLang === 'en') {
detectedLang.innerHTML = '🇬🇧 Inglese → 🇮🇹 Italiano';
detectedLang.className = 'detected-en';
} else {
detectedLang.textContent = 'Rilevamento automatico...';
detectedLang.className = '';
}
}
// Traduci testo
async function translateText() {
const text = inputText.value.trim();
if (!text) {
showToast('Inserisci del testo da tradurre', 'warning');
return;
}
const baseUrl = localStorage.getItem('ollamaBaseUrl') || 'http://localhost:11434';
const model = localStorage.getItem('ollamaModel');
if (!model) {
showToast('Configura un modello nelle impostazioni', 'warning');
openSettingsModal();
return;
}
// Rileva la lingua se non già rilevata
const detected = currentDetectedLang || detectLanguage(text);
if (!detected) {
showToast('Impossibile rilevare la lingua', 'warning');
return;
}
const direction = detected === 'en' ? 'en-it' : 'it-en';
isTranslating = true;
translateBtn.disabled = true;
translateBtn.innerHTML = `
<span class="spinner"></span>
<span class="btn-text">Traduzione...</span>
`;
statusIndicator.className = 'status-indicator translating';
try {
const result = await window.electronAPI.translateText({
text,
direction,
model,
baseUrl
});
if (result.success) {
outputText.value = result.translation.trim();
statusIndicator.className = 'status-indicator success';
showToast('Traduzione completata!', 'success');
} else {
outputText.value = '';
statusIndicator.className = 'status-indicator error';
showToast('Errore: ' + result.error, 'error');
}
} catch (error) {
outputText.value = '';
statusIndicator.className = 'status-indicator error';
showToast('Errore: ' + error.message, 'error');
} finally {
isTranslating = false;
translateBtn.disabled = false;
translateBtn.innerHTML = `
<span class="btn-text">Traduci</span>
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14"/>
<path d="M12 5l7 7-7 7"/>
</svg>
`;
}
}
// Aggiorna conteggio caratteri
function updateCharCount() {
const count = inputText.value.length;
charCount.textContent = `${count} caratter${count === 1 ? 'e' : 'i'}`;
}
// Copia negli appunti
async function copyToClipboard() {
const text = outputText.value;
if (!text) return;
try {
await navigator.clipboard.writeText(text);
showToast('Testo copiato!', 'success');
} catch (err) {
outputText.select();
document.execCommand('copy');
showToast('Testo copiato!', 'success');
}
}
// Pulisci input
function clearInput() {
inputText.value = '';
outputText.value = '';
updateCharCount();
statusIndicator.className = 'status-indicator';
currentDetectedLang = null;
detectedLang.textContent = 'Rilevamento automatico...';
detectedLang.className = '';
inputText.focus();
}
// Mostra toast
function showToast(message, type = 'info') {
toast.textContent = message;
toast.className = `toast show ${type}`;
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// Gestione modale
function openSettingsModal() {
settingsModal.classList.add('show');
loadAvailableModels();
}
function closeSettingsModal() {
settingsModal.classList.remove('show');
}
// Event Listeners
translateBtn.addEventListener('click', translateText);
inputText.addEventListener('input', () => {
updateCharCount();
detectLanguage(inputText.value);
});
clearBtn.addEventListener('click', clearInput);
copyBtn.addEventListener('click', copyToClipboard);
settingsBtn.addEventListener('click', openSettingsModal);
closeModal.addEventListener('click', closeSettingsModal);
refreshModels.addEventListener('click', loadAvailableModels);
saveSettings.addEventListener('click', saveSettingsData);
// Chiudi modale con ESC
settingsModal.addEventListener('click', (e) => {
if (e.target === settingsModal) {
closeSettingsModal();
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeSettingsModal();
}
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
translateText();
}
});
// Ascolta richiesta apertura impostazioni dal menu
window.electronAPI.onOpenSettings((event) => {
openSettingsModal();
});
// Inizializzazione
document.addEventListener('DOMContentLoaded', () => {
loadSettings();
updateCharCount();
});