336 lines
11 KiB
JavaScript
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();
|
|
});
|