Nessun oggetto della modifica
Nessun oggetto della modifica
Riga 4: Riga 4:


if (window.CommonTranslateUpdateLoaded) {
if (window.CommonTranslateUpdateLoaded) {
  console.warn("⚠️ CommonTranslateUpdate.js è già stato caricato.");
    console.warn("⚠️ CommonTranslateUpdate.js è già stato caricato.");
} else {
} else {
  window.CommonTranslateUpdateLoaded = true;
    window.CommonTranslateUpdateLoaded = true;
  window.linguaOrigine =
    window.linguaOrigine = localStorage.getItem("linguaOrigine") || document.documentElement.lang || "en";
    localStorage.getItem("linguaOrigine") ||
    document.documentElement.lang ||
    "en";


  // 🔒 usiamo solo il cameriere
    // 🔒 Niente più API key in chiaro → usiamo solo il cameriere
  window.OPENAI_PROXY_URL = "/dashboard/api/openai_project_gpt.php";
    window.OPENAI_PROXY_URL = "/dashboard/api/openai_project_gpt.php";


  console.log("✅ CommonTranslateUpdate.js caricato correttamente!");
    console.log("✅ CommonTranslateUpdate.js caricato correttamente!");
}
}


/* ======================= UTIL: rimuovi marcatori e righe sentinella ======================= */
/* ======================= Utility ======================= */
function stripMarkers(t) {
function stripMarkers(t) {
   return (t || "")
   return (t || "")
    // righe che contengono solo i marcatori (varie grafie)
     .replace(/^\s*[-–—]*\s*(TESTO\s*INIZIO|TEXT\s*START)\s*[-–—]*\s*$/gmi, "")
     .replace(/^\s*[-–—]*\s*(TESTO\s*INIZIO|TEXT\s*START)\s*[-–—]*\s*$/gmi, "")
     .replace(/^\s*[-–—]*\s*(TESTO\s*FINE|TEXT\s*END)\s*[-–—]*\s*$/gmi, "")
     .replace(/^\s*[-–—]*\s*(TESTO\s*FINE|TEXT\s*END)\s*[-–—]*\s*$/gmi, "")
    // marcatori inline stile --- TEXT START ---
     .replace(/---\s*(TESTO\s*INIZIO|TEXT\s*START)\s*---/gi, "")
     .replace(/---\s*(TESTO\s*INIZIO|TEXT\s*START)\s*---/gi, "")
     .replace(/---\s*(TESTO\s*FINE|TEXT\s*END)\s*---/gi, "")
     .replace(/---\s*(TESTO\s*FINE|TEXT\s*END)\s*---/gi, "")
Riga 30: Riga 25:
}
}


/* ======================= Segmentazione ======================= */
const HEADER_RE = /^={2,5}\s*([^=]+?)\s*={2,5}\s*$/;
const isBiblioHeader = (s) =>
  /^={2,5}\s*(bibliography|references|riferimenti)\b/i.test((s||"").trim());
const isListItem = (s) =>
  /^\s*(?:\d+\.\s+|[#*;:]+\s*)/.test(s||"");
 
/* ------- Segmentazione --------*/
function segmentaBloccoMediaWiki(wikitesto) {
function segmentaBloccoMediaWiki(wikitesto) {
  // 1) proteggi blocchi “pericolosi”
   const blocchiProtetti = [
   const blocchiProtetti = [
     /{{Tooltip\|[^{}]*?\|[^{}]*?\|<small>[\s\S]*?<\/small>\|?}}/g,
     /{{Tooltip\|[^{}]*?\|[^{}]*?\|<small>[\s\S]*?<\/small>\|?}}/g,
Riga 44: Riga 44:
   blocchiProtetti.forEach(rx => {
   blocchiProtetti.forEach(rx => {
     wikitesto = wikitesto.replace(rx, m => {
     wikitesto = wikitesto.replace(rx, m => {
       const key = `%%SEGMENTO_${idx++}%%`;
       const key = `%%SEGMENTO_${idx++}%%`; placeholderMap[key] = m; return key;
      placeholderMap[key] = m;
      return key;
     });
     });
   });
   });


  // 2) split per intestazioni vere: == H2/H3/H4 ==
   const parts = wikitesto.split(/\n(?=^={2,5}[^=\n]+={2,5}\s*$)/m);
   const parts = wikitesto.split(/\n(?=^={2,5}[^=\n]+={2,5}\s*$)/m);
  // 3) ricostruisci sezioni, trattando “Bibliography/References/Riferimenti” voce-per-voce
   const out = [];
   const out = [];
  const isBiblioHeader = (s) =>
    /^={2,5}\s*(bibliography|references|riferimenti)\b/i.test(s.trim());


   parts.forEach(section => {
   parts.forEach(section => {
    // reintegra i placeholder
     Object.entries(placeholderMap).forEach(([k, v]) => { section = section.replaceAll(k, v); });
     Object.entries(placeholderMap).forEach(([k, v]) => {
     out.push(section.trim());
      section = section.replaceAll(k, v);
    });
 
    if (isBiblioHeader(section)) {
      const [header, ...rest] = section.split(/\n/);
      const body = rest.join("\n");
      const items = body.match(/^\s*\d+\.\s[\s\S]*?(?=(?:^\s*\d+\.\s)|\Z)/gm);
      if (items && items.length) {
        out.push(header.trim());
        items.forEach(it => out.push(it.trim()));
      } else {
        out.push(section.trim());
      }
     } else {
      out.push(section.trim());
    }
   });
   });


Riga 84: Riga 61:
/* ======================= Ripristina segmenti salvati ======================= */
/* ======================= Ripristina segmenti salvati ======================= */
function ripristinaSegmenti(text) {
function ripristinaSegmenti(text) {
  if (!window.segmentiSalvati) return text;
    if (!window.segmentiSalvati) return text;
  return text.replace(/%%SEGMENTO_(\d+)%%/g, function (_m, index) {
    return text.replace(/%%SEGMENTO_(\d+)%%/g, function(match, index) {
    const segmento = window.segmentiSalvati[parseInt(index, 10)];
        const segmento = window.segmentiSalvati[parseInt(index)];
    return segmento ? segmento : _m;
        return segmento ? segmento : match;
  });
    });
}
}


/* ======================= AVVIO TRADUZIONE ======================= */
/* ======================= AVVIO TRADUZIONE ======================= */
async function traduciTestoUpdate() {
async function traduciTestoUpdate() {
  const conferma = confirm("⚡ Vuoi tradurre il capitolo?");
    const conferma = confirm("⚡ Vuoi tradurre il capitolo?");
  if (!conferma) return;
    if (!conferma) return;


  const linguaScelta = prompt(
    const linguaScelta = prompt("🌍 Scegli la lingua:\nit = Italiano\nen = Inglese\nfr = Francese\nes = Spagnolo\nde = Tedesco\ntutte = Tutte le lingue", "en");
    "🌍 Scegli la lingua:\nit = Italiano\nen = Inglese\nfr = Francese\nes = Spagnolo\nde = Tedesco\ntutte = Tutte le lingue",
    if (!linguaScelta) return;
    "en"
  );
  if (!linguaScelta) return;


  const lingue = ["it", "en", "fr", "es", "de"];
    const lingue = ["it", "en", "fr", "es", "de"];
  const lingueDaTradurre = linguaScelta === "tutte" ? lingue : [linguaScelta];
    const lingueDaTradurre = linguaScelta === "tutte" ? lingue : [linguaScelta];
  localStorage.setItem("lingueDaTradurre", JSON.stringify(lingueDaTradurre));
    localStorage.setItem("lingueDaTradurre", JSON.stringify(lingueDaTradurre));


  verificaURLCapitoli(lingueDaTradurre);
    verificaURLCapitoli(lingueDaTradurre);
}
}


function verificaURLCapitoli(lingueDaTradurre) {
function verificaURLCapitoli(lingueDaTradurre) {
  let formHtml = `<form id="urlForm">`;
    let formHtml = `<form id="urlForm">`;
  for (let lang of lingueDaTradurre) {
    for (let lang of lingueDaTradurre) {
    formHtml += `
        formHtml += `
      <label for="url_${lang}">${lang.toUpperCase()}:</label>
        <label for="url_${lang}">${lang.toUpperCase()}:</label>
      <input type="text" id="url_${lang}" value="${localStorage.getItem(
        <input type="text" id="url_${lang}" value="${localStorage.getItem(`urlCapitolo_${lang}`) || ""}" style="width:100%;margin-bottom:5px;"><br>`;
        `urlCapitolo_${lang}`
    }
      ) || ""}" style="width:100%;margin-bottom:5px;"><br>`;
    formHtml += `</form>`;
  }
  formHtml += `</form>`;


  let popup = document.createElement("div");
    let popup = document.createElement("div");
  popup.innerHTML = `
    popup.innerHTML = `
     <div id="popup-url-traduzione" style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%);
     <div id="popup-url-traduzione" style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%);
         background:white; padding:20px; border-radius:10px; box-shadow:0px 4px 6px rgba(0,0,0,0.2); z-index:2000; font-family:Arial, sans-serif;">
         background:white; padding:20px; border-radius:10px; box-shadow:0px 4px 6px rgba(0,0,0,0.2); z-index:2000; font-family:Arial, sans-serif;">
      <h3>🔍 Imposta gli URL per la traduzione</h3>
      <p>
        🧠 <b>Parole:</b> <span id="conteggio-parole">0</span><br>
        🔢 <b>Token stimati:</b> <span id="conteggio-token">0</span><br>
        💰 <b>Costo stimato:</b> <span id="costo-stimato">$0.0000</span>
      </p>
      ${formHtml}
      <div style="margin-top:10px; text-align:right;">
        <button id="salvaUrlBtn" type="button">✅ Salva URL</button>
        <button id="chiudiPopup" type="button">❌ Annulla</button>
      </div>
    </div>`;


  document.body.appendChild(popup);
        <h3>🔍 Imposta gli URL per la traduzione</h3>
 
        <p>
          🧠 <b>Parole:</b> <span id="conteggio-parole">0</span><br>
          🔢 <b>Token stimati:</b> <span id="conteggio-token">0</span><br>
          💰 <b>Costo stimato:</b> <span id="costo-stimato">$0.0000</span>
        </p>
 
        ${formHtml}


  const testo = ottieniTestoDaEditor();
        <div style="margin-top:10px; text-align:right;">
  const parole = testo.trim().split(/\s+/).length;
          <button id="salvaUrlBtn" type="button">✅ Salva URL</button>
  const tokenStimati = Math.ceil(parole / 0.75);
          <button id="chiudiPopup" type="button">❌ Annulla</button>
  const costo = (tokenStimati * 0.00002).toFixed(4);
        </div>
    </div>
    `;


  try {
     document.body.appendChild(popup);
     document.getElementById("conteggio-parole").innerText = parole;
    document.getElementById("conteggio-token").innerText = tokenStimati;
    document.getElementById("costo-stimato").innerText = `$${costo}`;
  } catch (e) {
    console.warn("⚠️ Conteggio parole/token non aggiornato:", e);
  }


  setTimeout(() => {
    const testo = ottieniTestoDaEditor();
     const salvaBtn = document.getElementById("salvaUrlBtn");
    const parole = testo.trim().split(/\s+/).length;
     const chiudiBtn = document.getElementById("chiudiPopup");
     const tokenStimati = Math.ceil(parole / 0.75);
     const costo = (tokenStimati * 0.00002).toFixed(4);


     if (salvaBtn) {
     try {
      salvaBtn.onclick = function () {
        document.getElementById("conteggio-parole").innerText = parole;
         lingueDaTradurre.forEach((lang) => {
         document.getElementById("conteggio-token").innerText = tokenStimati;
          const newUrl = document.getElementById(`url_${lang}`).value.trim();
        document.getElementById("costo-stimato").innerText = `$${costo}`;
          localStorage.setItem(`urlCapitolo_${lang}`, newUrl);
    } catch (e) {
        });
         console.warn("⚠️ Conteggio parole/token non aggiornato:", e);
         document.body.removeChild(popup);
        avviaTraduzioneConSegmentazione(lingueDaTradurre);
      };
     }
     }


     if (chiudiBtn) {
     setTimeout(() => {
      chiudiBtn.onclick = function () {
        const salvaBtn = document.getElementById("salvaUrlBtn");
        document.body.removeChild(popup);
        const chiudiBtn = document.getElementById("chiudiPopup");
      };
 
    }
        if (salvaBtn) {
  }, 100);
            salvaBtn.onclick = function () {
                lingueDaTradurre.forEach(lang => {
                    const newUrl = document.getElementById(`url_${lang}`).value.trim();
                    localStorage.setItem(`urlCapitolo_${lang}`, newUrl);
                });
                document.body.removeChild(popup);
                avviaTraduzioneConSegmentazione(lingueDaTradurre);
            };
        }
 
        if (chiudiBtn) {
            chiudiBtn.onclick = function () {
                document.body.removeChild(popup);
            };
        }
    }, 100);
}
}


function avviaTraduzioneConSegmentazione(lingueDaTradurre) {
function avviaTraduzioneConSegmentazione(lingueDaTradurre) {
  const testo = ottieniTestoDaEditor();
    const testo = ottieniTestoDaEditor();
  const blocchi = segmentaBloccoMediaWiki(testo);
    const blocchi = segmentaBloccoMediaWiki(testo);
  const errori = analizzaErroriSegmentazione(blocchi);
    const errori = analizzaErroriSegmentazione(blocchi);
  sostituisciTestoSegmentato(blocchi, errori);
    sostituisciTestoSegmentato(blocchi, errori);
  mostraPopupConferma(blocchi, errori, lingueDaTradurre);
    mostraPopupConferma(blocchi, errori, lingueDaTradurre);
}
}


/* ======================= Errori ed invio ======================= */
/*========= Errori ed invio ==========*/
 
function ottieniTestoDaEditor() {
function ottieniTestoDaEditor() {
  const area = document.getElementById("wpTextbox1");
    const area = document.getElementById("wpTextbox1");
  return area ? area.value.trim() : "";
    return area ? area.value.trim() : "";
}
}


function analizzaErroriSegmentazione(blocchi) {
function analizzaErroriSegmentazione(blocchi) {
  let errori = [];
    let errori = [];
  blocchi.forEach((b, i) => {
    blocchi.forEach((b, i) => {
    let err = [];
        let err = [];
    [["[", "]"], ["{", "}"], ["<", ">"]].forEach(([o, c]) => {
        [["[", "]"], ["{", "}"], ["<", ">"]].forEach(([o, c]) => {
      const countO = (b.match(new RegExp(`\\${o}`, "g")) || []).length;
            const countO = (b.match(new RegExp(`\\${o}`, "g")) || []).length;
      const countC = (b.match(new RegExp(`\\${c}`, "g")) || []).length;
            const countC = (b.match(new RegExp(`\\${c}`, "g")) || []).length;
      if (countO !== countC) err.push(`${o}${c} non bilanciate`);
            if (countO !== countC) err.push(`${o}${c} non bilanciate`);
        });
        if (err.length) errori.push({ index: i + 1, dettagli: err });
     });
     });
     if (err.length) errori.push({ index: i + 1, dettagli: err });
     return errori;
  });
  return errori;
}
}


function sostituisciTestoSegmentato(blocchi, errori) {
function sostituisciTestoSegmentato(blocchi, errori) {
  const area = document.getElementById("wpTextbox1");
    const area = document.getElementById("wpTextbox1");
  const finale = blocchi.map((b, i) => {
    const finale = blocchi.map((b, i) => {
    const e = errori.find((x) => x.index === i + 1);
        const e = errori.find(x => x.index === i + 1);
    const evidenza = e ? `⚠️ ERRORI:\n- ${e.dettagli.join("\n- ")}\n\n` : "";
        const evidenza = e ? `⚠️ ERRORI:\n- ${e.dettagli.join("\n- ")}\n\n` : "";
    return `${evidenza}🔹 BLOCCO ${i + 1}\n${b}`;
        return `${evidenza}🔹 BLOCCO ${i + 1}\n${b}`;
  });
    });
  area.value = finale.join("\n\n==============================\n\n");
    area.value = finale.join("\n\n==============================\n\n");
}
}


function mostraPopupConferma(blocchi, errori, lingueDaTradurre) {
function mostraPopupConferma(blocchi, errori, lingueDaTradurre) {
  const popup = document.createElement("div");
    const popup = document.createElement("div");
  popup.style = `
    popup.style = `
    position: fixed;
        position: fixed;
    bottom: 20px;
        bottom: 20px;
    right: 20px;
        right: 20px;
    background: white;
        background: white;
    padding: 15px;
        padding: 15px;
    border: 2px solid #444;
        border: 2px solid #444;
    box-shadow: 4px 4px 10px rgba(0,0,0,0.3);
        box-shadow: 4px 4px 10px rgba(0,0,0,0.3);
    z-index: 9999;
        z-index: 9999;
    resize: both;
        resize: both;
    overflow: auto;
        overflow: auto;
    cursor: move;`;
        cursor: move;`;
  popup.innerHTML = `
    popup.innerHTML = `
    <b>✅ Segmentazione completata</b><br>
        <b>✅ Segmentazione completata</b><br>
    🧠 Token stimati: ~${blocchi.length * 3000}<br>
        🧠 Token stimati: ~${blocchi.length * 3000}<br>
    💰 Costo approssimativo: ~$${(blocchi.length * 3000 * 0.00002).toFixed(2)}<br>
        💰 Costo approssimativo: ~$${(blocchi.length * 3000 * 0.00002).toFixed(2)}<br>
    ${errori.length > 0
        ${errori.length > 0
      ? `❗ <span style='color:red;'>${errori.length} errori rilevati. Procedere?</span><br>`
            ? `❗ <span style='color:red;'>${errori.length} errori rilevati. Procedere?</span><br>`
      : `<span style='color:green;'>✅ Nessun errore rilevato</span><br>`}
            : `<span style='color:green;'>✅ Nessun errore rilevato</span><br>`}
    <button id="btn-invia-traduzioni">✅ Invia Traduzioni</button>
        <button id="btn-invia-traduzioni">✅ Invia Traduzioni</button>
    <button id="btn-salva-segmentato">💾 Salva localmente</button>
        <button id="btn-salva-segmentato">💾 Salva localmente</button>
    <button onclick="this.parentNode.remove()">❌ Annulla</button>`;
        <button onclick="this.parentNode.remove()">❌ Annulla</button>`;


  document.body.appendChild(popup);
    document.body.appendChild(popup);
  dragElement(popup);
    dragElement(popup);


  document.getElementById("btn-invia-traduzioni").onclick = () => {
    document.getElementById("btn-invia-traduzioni").onclick = () => {
    popup.remove();
        popup.remove();
    lingueDaTradurre.forEach((lang) => inviaTraduzione(lang, blocchi));
        lingueDaTradurre.forEach(lang => inviaTraduzione(lang, blocchi));
  };
    };


  document.getElementById("btn-salva-segmentato").onclick = () => {
    document.getElementById("btn-salva-segmentato").onclick = () => {
    const testoSegmentato = document.getElementById("wpTextbox1").value.trim();
        const testoSegmentato = document.getElementById("wpTextbox1").value.trim();
    localStorage.setItem("segmentatoDaInviare", testoSegmentato);
        localStorage.setItem("segmentatoDaInviare", testoSegmentato);
    alert("✅ Testo segmentato salvato localmente!");
        alert("✅ Testo segmentato salvato localmente!");
  };
    };
}
}


function ricostruisciTestoSegmentato(blocchi) {
function ricostruisciTestoSegmentato(blocchi) {
  return blocchi
    return blocchi.map(b => {
    .map((b) =>
        return b.trim().match(/^={2,5}[^=]+={2,5}$/)
      b.trim().match(/^={2,5}[^=]+={2,5}$/)
            ? `\n\n${b.trim()}\n\n`
        ? `\n\n${b.trim()}\n\n`
            : b.trim();
        : b.trim()
     }).join("\n\n");
     )
    .join("\n\n");
}
}


/* ======================= INVIO AL CAMERIERE (MediaWiki API) ======================= */
/* ======================= INVIO AL CAMERIERE (speciale bibliografia) ======================= */
async function inviaTraduzione(lingua, blocchi) {
async function inviaTraduzione(lingua, blocchi){
   const urlAPI = "https://staging.masticationpedia.org/api.php";
   const urlAPI = "https://staging.masticationpedia.org/api.php";
  const urlDest = localStorage.getItem(`urlCapitolo_${lingua}`);
  if (!urlDest) return;


  const urlDestinazione = localStorage.getItem(`urlCapitolo_${lingua}`);
   const tokenRes = await fetch(`${urlAPI}?action=query&meta=tokens&type=csrf&format=json`, { credentials: "include" });
  if (!urlDestinazione) return;
 
  // CSRF token
   const tokenRes = await fetch(
    `${urlAPI}?action=query&meta=tokens&type=csrf&format=json`,
    { credentials: "include" }
  );
   const tokenData = await tokenRes.json();
   const tokenData = await tokenRes.json();
   const csrfToken = tokenData?.query?.tokens?.csrftoken;
   const csrfToken = tokenData?.query?.tokens?.csrftoken;


   // traduci ogni blocco via proxy, poi ricostruisci
   const tradotti = [];
   let testoTradotto = ricostruisciTestoSegmentato(
   let insideBiblio = false;
     await Promise.all(blocchi.map((b) => traduciBloccoProxy(b, lingua)))
 
   );
  for (const b of blocchi) {
  // ripristina placeholder e ripulisci marcatori residuali
    const trimmed = (b || "").trim();
 
     if (HEADER_RE.test(trimmed)) {
      insideBiblio = isBiblioHeader(trimmed);
      tradotti.push(b);
      continue;
    }
 
    if (insideBiblio && isListItem(trimmed)) {
      tradotti.push(b);
      continue;
    }
 
    tradotti.push(await traduciBloccoProxy(b, lingua));
  }
 
   let testoTradotto = ricostruisciTestoSegmentato(tradotti);
   testoTradotto = ripristinaSegmenti(testoTradotto);
   testoTradotto = ripristinaSegmenti(testoTradotto);
   testoTradotto = stripMarkers(testoTradotto);
   testoTradotto = stripMarkers(testoTradotto);


  // invia modifica
   const form = new URLSearchParams();
   const formData = new URLSearchParams();
   form.append("action", "edit");
   formData.append("action", "edit");
   form.append("title", urlDest.split("/").pop());
   formData.append("title", urlDestinazione.split("/").pop());
   form.append("text", testoTradotto);
   formData.append("text", testoTradotto);
   form.append("format", "json");
   formData.append("format", "json");
   form.append("token", csrfToken);
   formData.append("token", csrfToken);


   const response = await fetch(urlAPI, {
   const resp = await fetch(urlAPI, {
     method: "POST",
     method: "POST",
     headers: { "Content-Type": "application/x-www-form-urlencoded" },
     headers: { "Content-Type": "application/x-www-form-urlencoded" },
     body: formData,
     body: form,
     credentials: "include",
     credentials: "include"
   });
   });
 
   const data = await resp.json();
   const data = await response.json();
   if (data?.edit?.result === "Success") {
   if (data.edit && data.edit.result === "Success") {
     alert(`✅ Traduzione inviata con successo a: ${urlDest}`);
     alert(`✅ Traduzione inviata con successo a: ${urlDestinazione}`);
   } else {
   } else {
     console.error("❌ Errore invio:", data);
     console.error("❌ Errore invio:", data);
Riga 309: Riga 294:
}
}


/* ======================= Traduzione BLOCCO via cameriere ======================= */
/* ======================= TRADUZIONE BLOCCO VIA CAMERIERE ======================= */
async function traduciBloccoProxy(blocco, lingua) {
async function traduciBloccoProxy(blocco, lingua) {
  try {
    try {
    const res = await fetch(window.OPENAI_PROXY_URL, {
        const res = await fetch(window.OPENAI_PROXY_URL, {
      method: "POST",
            method: "POST",
      headers: { "Content-Type": "application/json" },
            headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
            body: JSON.stringify({
        model: "gpt-4o",
                model: "gpt-4o",
        prompt:
                prompt: `Translate this MediaWiki text into ${lingua}.  
          `Translate this MediaWiki text into ${lingua}. ` +
                Preserve ALL formatting, templates {{...}}, links [[...]], <math>, <ref>, etc.
          `Preserve ALL formatting, templates {{...}}, links [[...]], <math>, <ref>, etc. ` +
                Do not add explanations, comments or change syntax.
          `Do not add explanations, comments or change syntax.\n` +
                ${blocco}`
          `--- TEXT START ---\n${blocco}\n--- TEXT END ---`,
            })
      }),
        });
    });
        const data = await res.json();
 
        if (data.status !== "ok") throw new Error(data.error || "Errore proxy");
    const data = await res.json();
        return stripMarkers(data.result);
    if (data.status !== "ok") throw new Error(data.error || "Errore proxy");
    } catch (e) {
 
        console.error("❌ Traduzione fallita:", e);
    // 🧹 rimuovi eventuali marcatori riecheggiati
        return "[Errore Traduzione]";
    return stripMarkers(data.result);
    }
  } catch (e) {
    console.error("❌ Traduzione fallita:", e);
    return "[Errore Traduzione]";
  }
}
}


/* ======================= Drag helper ======================= */
function dragElement(elmnt) {
function dragElement(elmnt) {
  let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
    let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  elmnt.onmousedown = dragMouseDown;
    elmnt.onmousedown = dragMouseDown;
  function dragMouseDown(e) {
    function dragMouseDown(e) {
    e = e || window.event; e.preventDefault();
        e = e || window.event; e.preventDefault();
    pos3 = e.clientX; pos4 = e.clientY;
        pos3 = e.clientX; pos4 = e.clientY;
    document.onmouseup = closeDragElement;
        document.onmouseup = closeDragElement;
    document.onmousemove = elementDrag;
        document.onmousemove = elementDrag;
  }
    }
  function elementDrag(e) {
    function elementDrag(e) {
    e = e || window.event; e.preventDefault();
        e = e || window.event; e.preventDefault();
    pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY;
        pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY;
    pos3 = e.clientX; pos4 = e.clientY;
        pos3 = e.clientX; pos4 = e.clientY;
    elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
        elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
    elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
        elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  }
    }
  function closeDragElement() {
    function closeDragElement() {
    document.onmouseup = null;
        document.onmouseup = null;
    document.onmousemove = null;
        document.onmousemove = null;
  }
    }
}
}

Versione delle 15:08, 6 set 2025

/* ======================= CommonTranslateUpdate.js (INTEGRAZIONE COMPLETA con proxy) ======================= */

console.log("🔄 Caricamento CommonTranslateUpdate.js...");

if (window.CommonTranslateUpdateLoaded) {
    console.warn("⚠️ CommonTranslateUpdate.js è già stato caricato.");
} else {
    window.CommonTranslateUpdateLoaded = true;
    window.linguaOrigine = localStorage.getItem("linguaOrigine") || document.documentElement.lang || "en";

    // 🔒 Niente più API key in chiaro → usiamo solo il cameriere
    window.OPENAI_PROXY_URL = "/dashboard/api/openai_project_gpt.php";

    console.log("✅ CommonTranslateUpdate.js caricato correttamente!");
}

/* ======================= Utility ======================= */
function stripMarkers(t) {
  return (t || "")
    .replace(/^\s*[-–—]*\s*(TESTO\s*INIZIO|TEXT\s*START)\s*[-–—]*\s*$/gmi, "")
    .replace(/^\s*[-–—]*\s*(TESTO\s*FINE|TEXT\s*END)\s*[-–—]*\s*$/gmi, "")
    .replace(/---\s*(TESTO\s*INIZIO|TEXT\s*START)\s*---/gi, "")
    .replace(/---\s*(TESTO\s*FINE|TEXT\s*END)\s*---/gi, "")
    .trim();
}

const HEADER_RE = /^={2,5}\s*([^=]+?)\s*={2,5}\s*$/;
const isBiblioHeader = (s) =>
  /^={2,5}\s*(bibliography|references|riferimenti)\b/i.test((s||"").trim());
const isListItem = (s) =>
  /^\s*(?:\d+\.\s+|[#*;:]+\s*)/.test(s||"");

/* ------- Segmentazione --------*/
function segmentaBloccoMediaWiki(wikitesto) {
  const blocchiProtetti = [
    /{{Tooltip\|[^{}]*?\|[^{}]*?\|<small>[\s\S]*?<\/small>\|?}}/g,
    /{{Tooltip\|(?:[^{}]|{{[^{}]*}}|<[^>]*>|\[\[[^\]]+\]\]|%%[^%]+%%)+}}/g,
    /<ref[^>]*>[\s\S]*?<\/ref>/g,
    /<math>[\s\S]*?<\/math>/g,
    /<gallery[\s\S]*?<\/gallery>/g,
    /{{(?:[^{}]|\{\{[^{}]*\}\})*}}/g
  ];
  let placeholderMap = {}, idx = 0;
  blocchiProtetti.forEach(rx => {
    wikitesto = wikitesto.replace(rx, m => {
      const key = `%%SEGMENTO_${idx++}%%`; placeholderMap[key] = m; return key;
    });
  });

  const parts = wikitesto.split(/\n(?=^={2,5}[^=\n]+={2,5}\s*$)/m);
  const out = [];

  parts.forEach(section => {
    Object.entries(placeholderMap).forEach(([k, v]) => { section = section.replaceAll(k, v); });
    out.push(section.trim());
  });

  return out.filter(Boolean);
}

/* ======================= Ripristina segmenti salvati ======================= */
function ripristinaSegmenti(text) {
    if (!window.segmentiSalvati) return text;
    return text.replace(/%%SEGMENTO_(\d+)%%/g, function(match, index) {
        const segmento = window.segmentiSalvati[parseInt(index)];
        return segmento ? segmento : match;
    });
}

/* ======================= AVVIO TRADUZIONE ======================= */
async function traduciTestoUpdate() {
    const conferma = confirm("⚡ Vuoi tradurre il capitolo?");
    if (!conferma) return;

    const linguaScelta = prompt("🌍 Scegli la lingua:\nit = Italiano\nen = Inglese\nfr = Francese\nes = Spagnolo\nde = Tedesco\ntutte = Tutte le lingue", "en");
    if (!linguaScelta) return;

    const lingue = ["it", "en", "fr", "es", "de"];
    const lingueDaTradurre = linguaScelta === "tutte" ? lingue : [linguaScelta];
    localStorage.setItem("lingueDaTradurre", JSON.stringify(lingueDaTradurre));

    verificaURLCapitoli(lingueDaTradurre);
}

function verificaURLCapitoli(lingueDaTradurre) {
    let formHtml = `<form id="urlForm">`;
    for (let lang of lingueDaTradurre) {
        formHtml += `
        <label for="url_${lang}">${lang.toUpperCase()}:</label>
        <input type="text" id="url_${lang}" value="${localStorage.getItem(`urlCapitolo_${lang}`) || ""}" style="width:100%;margin-bottom:5px;"><br>`;
    }
    formHtml += `</form>`;

    let popup = document.createElement("div");
    popup.innerHTML = `
    <div id="popup-url-traduzione" style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%);
         background:white; padding:20px; border-radius:10px; box-shadow:0px 4px 6px rgba(0,0,0,0.2); z-index:2000; font-family:Arial, sans-serif;">

        <h3>🔍 Imposta gli URL per la traduzione</h3>

        <p>
          🧠 <b>Parole:</b> <span id="conteggio-parole">0</span><br>
          🔢 <b>Token stimati:</b> <span id="conteggio-token">0</span><br>
          💰 <b>Costo stimato:</b> <span id="costo-stimato">$0.0000</span>
        </p>

        ${formHtml}

        <div style="margin-top:10px; text-align:right;">
          <button id="salvaUrlBtn" type="button">✅ Salva URL</button>
          <button id="chiudiPopup" type="button">❌ Annulla</button>
        </div>
    </div>
    `;

    document.body.appendChild(popup);

    const testo = ottieniTestoDaEditor();
    const parole = testo.trim().split(/\s+/).length;
    const tokenStimati = Math.ceil(parole / 0.75);
    const costo = (tokenStimati * 0.00002).toFixed(4);

    try {
        document.getElementById("conteggio-parole").innerText = parole;
        document.getElementById("conteggio-token").innerText = tokenStimati;
        document.getElementById("costo-stimato").innerText = `$${costo}`;
    } catch (e) {
        console.warn("⚠️ Conteggio parole/token non aggiornato:", e);
    }

    setTimeout(() => {
        const salvaBtn = document.getElementById("salvaUrlBtn");
        const chiudiBtn = document.getElementById("chiudiPopup");

        if (salvaBtn) {
            salvaBtn.onclick = function () {
                lingueDaTradurre.forEach(lang => {
                    const newUrl = document.getElementById(`url_${lang}`).value.trim();
                    localStorage.setItem(`urlCapitolo_${lang}`, newUrl);
                });
                document.body.removeChild(popup);
                avviaTraduzioneConSegmentazione(lingueDaTradurre);
            };
        }

        if (chiudiBtn) {
            chiudiBtn.onclick = function () {
                document.body.removeChild(popup);
            };
        }
    }, 100);
}

function avviaTraduzioneConSegmentazione(lingueDaTradurre) {
    const testo = ottieniTestoDaEditor();
    const blocchi = segmentaBloccoMediaWiki(testo);
    const errori = analizzaErroriSegmentazione(blocchi);
    sostituisciTestoSegmentato(blocchi, errori);
    mostraPopupConferma(blocchi, errori, lingueDaTradurre);
}

/*========= Errori ed invio ==========*/

function ottieniTestoDaEditor() {
    const area = document.getElementById("wpTextbox1");
    return area ? area.value.trim() : "";
}

function analizzaErroriSegmentazione(blocchi) {
    let errori = [];
    blocchi.forEach((b, i) => {
        let err = [];
        [["[", "]"], ["{", "}"], ["<", ">"]].forEach(([o, c]) => {
            const countO = (b.match(new RegExp(`\\${o}`, "g")) || []).length;
            const countC = (b.match(new RegExp(`\\${c}`, "g")) || []).length;
            if (countO !== countC) err.push(`${o}${c} non bilanciate`);
        });
        if (err.length) errori.push({ index: i + 1, dettagli: err });
    });
    return errori;
}

function sostituisciTestoSegmentato(blocchi, errori) {
    const area = document.getElementById("wpTextbox1");
    const finale = blocchi.map((b, i) => {
        const e = errori.find(x => x.index === i + 1);
        const evidenza = e ? `⚠️ ERRORI:\n- ${e.dettagli.join("\n- ")}\n\n` : "";
        return `${evidenza}🔹 BLOCCO ${i + 1}\n${b}`;
    });
    area.value = finale.join("\n\n==============================\n\n");
}

function mostraPopupConferma(blocchi, errori, lingueDaTradurre) {
    const popup = document.createElement("div");
    popup.style = `
        position: fixed;
        bottom: 20px;
        right: 20px;
        background: white;
        padding: 15px;
        border: 2px solid #444;
        box-shadow: 4px 4px 10px rgba(0,0,0,0.3);
        z-index: 9999;
        resize: both;
        overflow: auto;
        cursor: move;`;
    popup.innerHTML = `
        <b>✅ Segmentazione completata</b><br>
        🧠 Token stimati: ~${blocchi.length * 3000}<br>
        💰 Costo approssimativo: ~$${(blocchi.length * 3000 * 0.00002).toFixed(2)}<br>
        ${errori.length > 0
            ? `❗ <span style='color:red;'>${errori.length} errori rilevati. Procedere?</span><br>`
            : `<span style='color:green;'>✅ Nessun errore rilevato</span><br>`}
        <button id="btn-invia-traduzioni">✅ Invia Traduzioni</button>
        <button id="btn-salva-segmentato">💾 Salva localmente</button>
        <button onclick="this.parentNode.remove()">❌ Annulla</button>`;

    document.body.appendChild(popup);
    dragElement(popup);

    document.getElementById("btn-invia-traduzioni").onclick = () => {
        popup.remove();
        lingueDaTradurre.forEach(lang => inviaTraduzione(lang, blocchi));
    };

    document.getElementById("btn-salva-segmentato").onclick = () => {
        const testoSegmentato = document.getElementById("wpTextbox1").value.trim();
        localStorage.setItem("segmentatoDaInviare", testoSegmentato);
        alert("✅ Testo segmentato salvato localmente!");
    };
}

function ricostruisciTestoSegmentato(blocchi) {
    return blocchi.map(b => {
        return b.trim().match(/^={2,5}[^=]+={2,5}$/)
            ? `\n\n${b.trim()}\n\n`
            : b.trim();
    }).join("\n\n");
}

/* ======================= INVIO AL CAMERIERE (speciale bibliografia) ======================= */
async function inviaTraduzione(lingua, blocchi){
  const urlAPI = "https://staging.masticationpedia.org/api.php";
  const urlDest = localStorage.getItem(`urlCapitolo_${lingua}`);
  if (!urlDest) return;

  const tokenRes = await fetch(`${urlAPI}?action=query&meta=tokens&type=csrf&format=json`, { credentials: "include" });
  const tokenData = await tokenRes.json();
  const csrfToken = tokenData?.query?.tokens?.csrftoken;

  const tradotti = [];
  let insideBiblio = false;

  for (const b of blocchi) {
    const trimmed = (b || "").trim();

    if (HEADER_RE.test(trimmed)) {
      insideBiblio = isBiblioHeader(trimmed);
      tradotti.push(b); 
      continue;
    }

    if (insideBiblio && isListItem(trimmed)) {
      tradotti.push(b); 
      continue;
    }

    tradotti.push(await traduciBloccoProxy(b, lingua));
  }

  let testoTradotto = ricostruisciTestoSegmentato(tradotti);
  testoTradotto = ripristinaSegmenti(testoTradotto);
  testoTradotto = stripMarkers(testoTradotto);

  const form = new URLSearchParams();
  form.append("action", "edit");
  form.append("title", urlDest.split("/").pop());
  form.append("text", testoTradotto);
  form.append("format", "json");
  form.append("token", csrfToken);

  const resp = await fetch(urlAPI, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: form,
    credentials: "include"
  });
  const data = await resp.json();
  if (data?.edit?.result === "Success") {
    alert(`✅ Traduzione inviata con successo a: ${urlDest}`);
  } else {
    console.error("❌ Errore invio:", data);
  }
}

/* ======================= TRADUZIONE BLOCCO VIA CAMERIERE ======================= */
async function traduciBloccoProxy(blocco, lingua) {
    try {
        const res = await fetch(window.OPENAI_PROXY_URL, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                model: "gpt-4o",
                prompt: `Translate this MediaWiki text into ${lingua}. 
                Preserve ALL formatting, templates {{...}}, links [[...]], <math>, <ref>, etc.
                Do not add explanations, comments or change syntax.
                ${blocco}`
            })
        });
        const data = await res.json();
        if (data.status !== "ok") throw new Error(data.error || "Errore proxy");
        return stripMarkers(data.result);
    } catch (e) {
        console.error("❌ Traduzione fallita:", e);
        return "[Errore Traduzione]";
    }
}

function dragElement(elmnt) {
    let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
    elmnt.onmousedown = dragMouseDown;
    function dragMouseDown(e) {
        e = e || window.event; e.preventDefault();
        pos3 = e.clientX; pos4 = e.clientY;
        document.onmouseup = closeDragElement;
        document.onmousemove = elementDrag;
    }
    function elementDrag(e) {
        e = e || window.event; e.preventDefault();
        pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY;
        pos3 = e.clientX; pos4 = e.clientY;
        elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
        elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
    }
    function closeDragElement() {
        document.onmouseup = null;
        document.onmousemove = null;
    }
}