1//if(debug)console.log(window.contextPathLiferay); 2 let iphoneDevice = false; 3 4if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) 5 { iphoneDevice = true;} 6 7const API_SERVER = "https://dediapi.calabriadigital.it"; // URL del server Python 8 let clingoReady = false; 9 let debug = false; // Variabile debug per mostrare/nascondere elementi di sviluppo 10 const frasiStandard = { 11 aspPrompt: [ 12 "Sto analizzando la tua richiesta, ti risponderò a breve", 13 "Un attimo, raccolgo le informazioni necessarie per rispondere", 14 "Sto cercando una risposta alla tua domanda, ci siamo quasi", 15 "Dammi solo qualche secondo per elaborare la tua richiesta", 16 "Mi metto subito alla ricerca della risposta alla tua domanda", 17 "Cerco la risposta alla tua domanda nelle informazioni disponibili", 18 "Sto cercando le informazioni nella mia base di conoscenza", 19 ], 20 langNatPrompt: [ 21 "Sto formulando una risposta personalizzata", 22 "Preparo una risposta dettagliata per te", 23 "Sto organizzando le informazioni trovate", 24 "Sto sintetizzando la risposta alla tua domanda", 25 "Rendo più chiara la risposta per te", 26 "Sto componendo la risposta alla tua domanda", 27 "Sto sistemando le informazioni per darti una risposta chiara e precisa" 28 ], 29 langNatRagPrompt: [ 30 "Sto approfondendo la ricerca, potrebbe volerci qualche istante in più", 31 "Non ho trovato subito la risposta, ma continuo a cercare", 32 "Cerco informazioni aggiuntive, resta con me ancora un attimo", 33 "Sto esplorando altre fonti per trovare ciò che chiedi, ci siamo quasi", 34 "La risposta non è immediata, ma non mi arrendo", 35 "Sto facendo una ricerca più approfondita, grazie per la pazienza", 36 "Non ho ancora trovato una risposta, ma continuo a cercare" 37 ] 38 } 39 40 // Funzione per caricare dinamicamente clingo.js 41 async function loadClingo() { 42 try { 43 // Initialize clingo with default WASM URL 44 await clingo.init(Liferay.ThemeDisplay.getPortalURL()+window.contextPathLiferay+"/assets/wasm/clingo_5m.wasm"); 45 46 clingoReady = true; 47 48 // Test run to ensure it's working 49 if(debug)console.log("Clingo test:", await clingo.run("a. b :- a.")); 50 return true; 51 } catch (error) { 52 console.error("Failed to initialize Clingo:", error); 53 throw error; 54 } 55 } 56 57 async function restartClingo() { 58 if(debug)console.log("Restarting Clingo..."); 59 await clingo.restart(Liferay.ThemeDisplay.getPortalURL()+window.contextPathLiferay+"/assets/wasm/clingo_5m.wasm"); // re-initialize Clingo 60 } 61 62 63 // Inizializza il sistema al caricamento della pagina 64 async function initializeSystem() { 65 const status = document.querySelector('.app-container.dedi .status'); 66 const statusLed = document.querySelector('.app-container.dedi .status-led'); 67 try { 68 status.textContent = "Caricamento Clingo WASM..."; 69 statusLed.className = "status-led orange d-none"; 70 document.getElementById("logoDediIcon").style.color = "#FFA500"; 71 updateEyesAndMouth("orange"); 72 await loadClingo(); 73 status.textContent = "Pronto"; 74 statusLed.className = "status-led green d-none"; 75 document.getElementById("logoDediIcon").style.color = "#0F400F"; 76 updateEyesAndMouth("green"); 77 if(debug)console.log("Sistema inizializzato correttamente"); 78 79 // Mostra il pulsante della base di conoscenza solo se debug è true 80 if (debug) { 81 document.querySelector('.app-container.dedi .kb-toggle').style.display = 'inline-block'; 82 } 83 84 } catch (error) { 85 status.textContent = "Errore nel caricamento di Clingo"; 86 statusLed.className = "status-led red d-none"; 87 document.getElementById("logoDediIcon").style.color = "#FF0000"; 88 updateEyesAndMouth("red"); 89 console.error("Errore inizializzazione:", error); 90 } 91 } 92 93 //aggiungi dei fatti alla textarea dei fatti partendo da un file txt 94 function addTextFromFile(fileContent) { 95 const factsArea = document.querySelector('.app-container.dedi #factsArea'); 96 factsArea.value += fileContent + '\n'; 97 } 98
99 //leggi un file di testo partendo da un url e aggiungi i fatti alla textarea dei fatti 100 async function readAspFromFile(url, toLowerCase = false) { 101 try { 102 const response = await fetch(url); 103 if (!response.ok) throw new Error("Errore nel caricamento del file"); 104 let text = await response.text(); 105 if (toLowerCase) { 106 text = text.toLowerCase(); 107 } 108 addTextFromFile(text); 109 return text; 110 } catch (error) { 111 console.error("Errore nella lettura del file:", error); 112 } 113 } 114 115 async function readTextFromFile(url) { 116 try { 117 const response = await fetch(url); 118 if (!response.ok) throw new Error("Errore nel caricamento del file"); 119 const text = await response.text(); 120 if(debug)console.log("Contenuto del file:", text); 121 return text; 122 } catch (error) { 123 console.error("Errore nella lettura del file:", error); 124 } 125 } 126 127 // Esegui la lettura del file di fatti all'avvio 128 readAspFromFile(window.contextPathLiferay+'/assets/programs/baseProgram.lp'); 129 // readAspFromFile('facts.lp'); 130 readAspFromFile('https://calabriadigital.it/o/blade/servlet/dailyfacts?outputType=calabria-digital',true) 131 132 //funzione che prende 5 risposte casuali 133 function getRandomAnswers(answers) { 134 const shuffled = answers.sort(() => 0.5 - Math.random()); 135 return shuffled.slice(0, 10); 136 } 137 138 // Chiamata al server Python per LLM 139 async function callLLM(llm, prompt, systemPromptFile = null, systemPrompt = "", temperature = 0.2, previousInteraction=null) { 140 try { 141 const response = await axios.post(`${API_SERVER}/api/call-llm`, { 142 llm: llm, 143 prompt: prompt, 144 systemPromptFile: systemPromptFile, 145 temperature: temperature, 146 previousInteraction: previousInteraction 147 }); 148 149 return response.data; 150 } catch (error) { 151 console.error("Errore nella chiamata LLM:", error); 152 return { 153 success: false, 154 message: error.response?.data?.message || "Errore nella chiamata al server" 155 }; 156 } 157 } 158 159 const settedModel = "claude"; // Modello predefinito 160 //chiamata default 161 async function callPresetted(prompt, systemPromptFile, usePreviousInteraction=true) { 162 let previousInteraction = null; 163 if (usePreviousInteraction) { 164 // Prendi l'ultima interazione dalla chat 165 previousInteraction = generateLatestInteractionJson(); 166 } 167 return await callLLM(settedModel, prompt, systemPromptFile, null, 0.1, previousInteraction); 168 } 169 170 171 172 173 // Chiamata a Claude 174 async function callClaude(prompt, systemPromptFile) { 175 return await callLLM("claude", prompt, systemPromptFile, null, 0.1); 176 } 177 178 // Chiamata a DeepSeek 179 async function callDeepSeek(prompt, systemPromptFile) { 180 return await callLLM("deepseek", prompt, systemPromptFile, null, 0.1); 181 } 182 183 // Esegui programma ASP con Clingo 184 async function executeASP(program) { 185 if (!clingoReady) { 186 throw new Error("Clingo non inizializzato"); 187 } 188 189 try { 190 const result = await clingo.run(program); 191 return result; 192 } catch (error) { 193 throw new Error("Errore esecuzione ASP: " + error.message); 194 } 195 } 196 197 198 generateLatestInteractionJson = function (){ 199 //prendi l'ultima interazione dalla chat 200 const chatBox = document.querySelector('.app-container.dedi .chat-box'); 201 const userMessages = Array.from(chatBox.querySelectorAll('.message.user-message')); 202 const botMessages = Array.from(chatBox.querySelectorAll('.message.bot-message:not(.typing-indicator)')); 203 //se la dimensione di botmessages è 1 ritorna 204 if( botMessages.length === 1) { 205 return null; 206 } 207 //se la dimensione di userMessages è >= 1 e bot messages è maggiore di 1 crea un json {userMessage: userMe
207ssage, botMessage: botMessage} 208 if (userMessages.length > 1 && botMessages.length > 1) { 209 if(debug)console.log("botMessages:", botMessages); 210 const userMessage = userMessages[userMessages.length - 2].innerHTML.trim(); 211 const botMessage = botMessages[botMessages.length - 1].innerHTML.trim(); 212 if(debug)console.log("Ultima interazione:", { userMessage: userMessage, botMessage: botMessage }); 213 return JSON.stringify({ userMessage: userMessage, botMessage: botMessage }); 214 } 215 } 216 217 // Interpreta i risultati ASP 218 async function interpretResults(question, results) { 219 if(debug)console.log("Risposte:"+ results); 220 221 // Check if there are no valid results 222 if (!results || results.Result !== "SATISFIABLE" || !results.Call || 223 !results.Call[0] || !results.Call[0].Witnesses || 224 results.Call[0].Witnesses.length === 0) { 225 return `{"question": "${question}", "results": []}`; 226 } 227 228 // Extract answer values from the witnesses 229 const values = results.Call[0].Witnesses[0].Value || []; 230 231 //converti la values in una stringa con i fatti separati da . e \n 232 let valuesString = "" 233 for (let i = 0; i < values.length; i++) { 234 valuesString += values[i]+ '.\n'; 235 } 236 if(debug)console.log("Risposte:"+ valuesString); 237 238 const eventiResultsAsp = await executeASP(valuesString+'#show risposta/15.'); 239 const eventiResults = eventiResultsAsp.Call[0].Witnesses[0].Value || []; 240 241 const luoghiResultsAsp = await executeASP(valuesString+'#show risposta/4.'); 242 const luoghiResults = luoghiResultsAsp.Call[0].Witnesses[0].Value || []; 243 244 //prendi tutte le risposte senza filtri 245 const answers = getRandomAnswers(eventiResults).concat(getRandomAnswers(luoghiResults)); 246 //sostituisci " con ' per evitare problemi di escape 247 for (let i = 0; i < answers.length; i++) { 248 answers[i] = answers[i].replace(/"/g, "'"); 249 } 250 //rimuovi caratteri speciali dalla questione 251 //rimuovi virgolette dalla questione 252 question = question.replace(/"/g, " "); 253 //rimuuovi apici dalla questione 254 question = question.replace(/'/g, ' '); 255 // Escape the answers for JSON 256 for (let i = 0; i < answers.length; i++) { 257 } 258 259 return answers.length > 0 260 ? `{"question": "${question}", "results": ["${answers.join('","')}"]}` 261 : `{"question": "${question}", "results": []}`; 262 } 263 264 // Funzione per aggiungere un messaggio alla chat 265 function addMessage(text, isUser) { 266 const chatBox = document.querySelector('.app-container.dedi .chat-box'); 267 const messageDiv = document.createElement('div'); 268 messageDiv.className = isUser ? 'message user-message' : 'message bot-message'; 269 270 // Aggiungi il contenuto come HTML invece di testo semplice 271 messageDiv.innerHTML = text; 272 273 // Clear both sides to handle float properly 274 const clearfix = document.createElement('div'); 275 clearfix.className = 'clearfix'; 276 277 chatBox.appendChild(messageDiv); 278 chatBox.appendChild(clearfix); 279 chatBox.scrollTop = chatBox.scrollHeight; 280 } 281 282 // Mostra un indicatore di caricamento mentre il bot sta elaborando 283 function showTypingIndicator() { 284 const chatBox = document.querySelector('.app-container.dedi .chat-box'); 285 const loadingDiv = document.createElement('div'); 286 loadingDiv.className = 'message bot-message typing-indicator'; 287 loadingDiv.id = 'typingIndicator'; 288 loadingDiv.innerHTML = '<span>.</span><span>.</span><span>.</span>'; 289 chatBox.appendChild(loadingDiv); 290 chatBox.scrollTop = chatBox.scrollHeight; 291 // Aggiungi un messaggio all'indicatore di caricamento 292 addMessageToIndicator(frasiStandard.aspPrompt[Math.floor(Math.random() * frasiStandard.aspPrompt.length)]); 293 294 295 } 296 297 function addMessageToIndicator(text) { 298 const indicator = document.querySelector('.app-container.dedi #typingIndicator'); 299 if (indicator) { 300 // Aggiungi il testo all'indicatore di caricamento 301 // Stream the text character by character 302 let i = 0; 303 function stream() { 304 if (i <= text.length) { 305 indicator.innerHTML = text.slice(0, i); 306 i++; 307 setTimeout(stream, 30); 308 } 309 else { 310 indicator.innerHTML += '<span>.</span><span>.</span><span>.</span>'; 311 312 } 313 } 314 stream(); 315 //aggiungi i puntini di caricamento '<div><span>.</span><span>.</span><span>.</span>' 316 317 318 } 319 } 320 321 // Rimuovi l'indicatore di caricamento 322 function removeTypingIndicator() { 323 const indicator = document.querySelector('.app-container.dedi #typingIndicator'); 324 if (indicator) { 325 indicator.remove(); 326 } 327 } 328 329 // Funzione per aggiornare lo stato degli occhi e della bocca 330 function updateEyesAndMouth(state) { 331 const leftEye = document.getElementById("left-eye"); 332 const rightEye = document.getElementById("right-eye"); 333 const mouth = document.getElementById("mouth"); 334 335 if (state === "green") { 336 // Blink animation 337 leftEye.setAttribute("height", "22.0675"); 338 rightEye.setAttribute("height", "22.0675"); 339 leftEye.setAttribute("y", "97.9485"); 340 rightEye.setAttribute("y", "97.9485"); 341 leftEye.style.animation = "blink_dedi 2s infinite"; 342 rightEye.style.animation = "blink_dedi 2s infinite"; 343 mouth.style.display = "none"; 344 } else if (state === "orange") { 345 // Thinking animation with blink_dedi 346 leftEye.setAttribute("height", "22.0675"); 347 rightEye.setAttribute("height", "22.0675"); 348 leftEye.setAttribute("y", "97.9485"); 349 rightEye.setAttribute("y", "97.9485"); 350 leftEye.style.animation = "blink_dedi 2s infinite, look-around 2s infinite"; 351 rightEye.style.animation = "blink_dedi 2s infinite, look-around 2s infinite"; 352 mouth.style.display = "none"; 353 } else if (state === "red") { 354 // Closed eyes 355 leftEye.setAttribute("height", "2"); 356 rightEye.setAttribute("height", "2"); 357 leftEye.setAttribute("y", "108.9485"); 358 rightEye.setAttribute("y", "108.9485"); 359 leftEye.style.animation = ""; 360 rightEye.style.animation = ""; 361 mouth.style.display = "none"; 362 } else if (state === "speaking") { 363 // Speaking animation with blink_dedi, eyes stay still 364 leftEye.setAttribute("height", "22.0675"); 365 rightEye.setAttribute("height", "22.0675"); 366 leftEye.setAttribute("y", "97.9485"); 367 rightEye.setAttribute("y", "97.9485"); 368 leftEye.style.animation = "blink_dedi 2s infinite"; 369 rightEye.style.animation = "blink_dedi 2s infinite"; 370 mouth.style.display = "block"; 371 mouth.style.animation = "speak 0.5s infinite"; 372 } 373 } 374 375 // Aggiungi animazioni per blink_dedi, look-around e speak 376 const style = document.createElement("style"); 377 style.textContent = ` 378 @keyframes blink_dedi { 379 0%, 20%, 100% { height: 22.0675px; } 380 10% { height: 2px; } 381 0%, 100% { transform: translate(0, 0); } 382 25% { transform: translate(-1px, -0px); } 383 50% { transform: translate(1px, 0px); } 384 75% { transform: translate(-1px, 0px); } 385 } 386 @keyframes look-around { 387 0%, 100% { transform: translate(0, -20px); } 388 25% { transform: translate(-10px, -25px); } 389 50% { transform: translate(0px,-30px); } 390 75% { transform: translate(10px, -25px); } 391 } 392 @keyframes speak { 393 0%, 100% { height: 5px; transform: translateY(0) rotate(0deg); } 394 25% { height: 13px; transform: translateY(-2px) rotate(-2deg); } 395 50% { height: 5px; transform: translateY(2px) rotate(2deg); } 396 75% { height: 12px; transform: translateY(-1px) rotate(-1deg); } 397 } 398 `; 399 document.head.appendChild(style); 400 401 // Funzione per aggiungere un messaggio alla chat come stream 402 async function addMessageStream(text, isUser) { 403 const chatBox = document.querySelector('.app-container.dedi .chat-box'); 404 const messageDiv = document.createElement('div'); 405 messageDiv.className = isUser ? 'message user-message' : 'message bot-message'; 406 chatBox.appendChild(messageDiv); 407 408 if (!isUser) { 409 updateEyesAndMouth("speaking"); 410 } 411 412 for (let i = 0; i < text.length; i++) { 413 await new Promise(resolve => setTimeout(resolve, 20)); // Simula lo streaming 414 messageDiv.innerHTML += text[i]; 415 chatBox.scrollTop = chatBox.scrollHeight; 416 } 417 418 if (!isUser) { 419 updateEyesAndMouth("green"); 420 } 421 } 422 423 // Aggiorna lo stato degli occhi in base al colore 424 function updateState(color) { 425 document.getElementById("logoDediIcon").style.color = color; 426 if (color === "#0F400F") updateEyesAndMouth("green"); 427 else if (color === "#FFA500") updateEyesAndMouth("orange"); 428 else if (color === "#FF0000") updateEyesAndMouth("red"); 429 } 430 431 // Funzione per analizzare l'HTML, creare la struttura con ID e caricare il testo come stream 432 async function addMessageStreamHTMLWithIds(htmlContent, isUser) { 433 const chatBox = document.querySelector('.app-container.dedi .chat-box'); 434 const messageDiv = document.createElement('div'); 435 messageDiv.className = isUser ? 'message user-message' : 'message bot-message'; 436 chatBox.appendChild(messageDiv); 437 438 if (!isUser) { 439 updateEyesAndMouth("speaking"); 440 document.getElementById("logoDediIcon").style.color = "#0F400F"; // Passa a stato verde 441 } 442 443 const parser = new DOMParser(); 444 const parsedHTML = parser.parseFromString(htmlContent, 'text/html'); 445 const elements = Array.from(parsedHTML.body.childNodes); 446 447 let idCounter = 0; // Contatore per assegnare ID univoci 448 449 // Funzione ricorsiva per creare la struttura HTML con ID e aggiungere la classe d-none 450 function createElementWithIds(element) { 451 if (element.nodeType === Node.ELEMENT_NODE) { 452 const newElement = document.createElement(element.nodeName); 453 newElement.id = `answer${idCounter++}`; // Assegna un ID univoco 454 newElement.classList.add('d-none'); // Aggiungi la classe d-none 455 for (const attr of element.attributes) { 456 newElement.setAttribute(attr.name, attr.value); 457 }
458 element.childNodes.forEach(child => { 459 const childElement = createElementWithIds(child); 460 if (childElement) newElement.appendChild(childElement); 461 }); 462 return newElement; 463 } else if (element.nodeType === Node.TEXT_NODE) { 464 return document.createTextNode(''); 465 } 466 return null; 467 } 468 469 // Funzione ricorsiva per caricare il testo come stream, rimuovere la classe d-none e l'ID 470 async function streamTextContent(element, originalElement) { 471 if (element.nodeType === Node.ELEMENT_NODE) { 472 element.classList.remove('d-none'); // Rimuovi la classe d-none 473 for (let i = 0; i < element.childNodes.length; i++) { 474 await streamTextContent(element.childNodes[i], originalElement.childNodes[i]); 475 } 476 element.removeAttribute('id'); // Rimuovi l'ID una volta completato lo streaming 477 } else if (element.nodeType === Node.TEXT_NODE) { 478 const textContent = originalElement.textContent || ''; 479 for (let i = 0; i < textContent.length; i++) { 480 await new Promise(resolve => setTimeout(resolve, 10)); // Simula lo streaming 481 element.textContent += textContent[i]; 482 chatBox.scrollTop = chatBox.scrollHeight; 483 } 484 } 485 } 486 487 // Creazione della struttura HTML con ID 488 for (const element of elements) { 489 const newElement = createElementWithIds(element); 490 if (newElement) { 491 messageDiv.appendChild(newElement); 492 await streamTextContent(newElement, element); // Caricamento del testo come stream 493 } 494 } 495 496 if (!isUser) { 497 updateEyesAndMouth("green"); 498 } 499 } 500 501 // Gestione dell'interfaccia 502 document.addEventListener('DOMContentLoaded', () => { 503 initializeSystem(); 504 505 506 507 // URL param per attivare debug mode 508 const urlParams = new URLSearchParams(window.location.search); 509 if (urlParams.get('debug') === 'true') { 510 debug = true; 511 document.querySelector('.app-container.dedi .kb-toggle').style.display = 'inline-block'; 512 } 513 514 // Toggle per mostrare/nascondere la base di conoscenza 515 document.querySelector('.app-container.dedi .kb-toggle').addEventListener('click', () => { 516 const kbSection = document.querySelector('.app-container.dedi .kb-section'); 517 kbSection.classList.toggle('visible'); 518 }); 519 520 document.querySelector('.app-container.dedi .send-button').addEventListener('click', async () => { 521 const userInput = document.querySelector('.app-container.dedi #userInput').value; 522 let facts = document.querySelector('.app-container.dedi #factsArea').value; 523 524 //aggiungi un fatto con la data di oggi 525 const today = new Date(); 526 const day = String(today.getDate()).padStart(1, '0');
527 const month = String(today.getMonth() + 1).padStart(1, '0'); 528 const year = today.getFullYear(); 529 const hours = String(today.getHours()).padStart(1, '0'); 530 const minutes = String(today.getMinutes()).padStart(1, '0'); 531 const oggi = `oggi(${year}, ${month}, ${day}, ${hours}, ${minutes}).`; 532 533 //rendi i fatti case insensitive 534 const status = document.querySelector('.app-container.dedi .status'); 535 const statusLed = document.querySelector('.app-container.dedi .status-led'); 536 537 if (!userInput.trim()) return; 538 539 // Aggiungi messaggio utente 540 addMessage(userInput, true); 541 542 document.querySelector('.app-container.dedi #userInput').value = ''; 543 status.textContent = "Elaborazione in corso..."; 544 statusLed.className = "status-led orange d-none"; 545 document.getElementById("logoDediIcon").style.color = "#FFA500"; 546 updateEyesAndMouth("orange"); 547 showTypingIndicator(); 548 549 // Blocca input e bottone invio durante l'elaborazione 550 setChatInputEnabled(false); 551 552 try { 553 if (!clingoReady) { 554 throw new Error("Attendere il completamento del caricamento"); 555 } 556 557 // Step 1: Genera query ASP 558 const userInputLower = userInput.toLowerCase().replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' '); 559 const userInputLowerWithDate = "%genera il programma in sintassi clingo per ottenere informazioni su: "+userInputLower + ' ' + oggi; 560 561 // Chiamata al server Python per generare la query ASP 562 const llmResponse = await callPresetted(userInputLowerWithDate, "aspGen.prompt"); 563 if(debug)console.log("LLM Response Asp:", llmResponse); 564 565 566 if (!llmResponse.success) { 567 removeTypingIndicator(); 568 addMessage("Non riesco a contattare il sistema di IA, sto ricevendo troppe richieste. Riprova fra qualche minuto..", false); 569 status.textContent = "In Attesa"; 570 statusLed.className = "status-led red d-none"; 571 document.getElementById("logoDediIcon").style.color = "#ff0000"; 572 updateEyesAndMouth("red"); 573 setTimeout(function(){ 574 document.getElementById("logoDediIcon").style.color = "#0F400F"; 575 updateEyesAndMouth("green"); 576 setChatInputEnabled(true); 577 status.textContent = "Pronto"; 578 statusLed.className = "status-led green d-none"; 579 580 581 },15000) 582 return; 583 } 584 585 // Step 2: Esegui query 586 const fullProgram = facts+'\n'+oggi+'\n'+llmResponse.aspQuery; 587 if(debug)console.log(fullProgram); 588 let aspResults; 589 let maxRetries = 2; 590 let currentProgram = fullProgram; 591 let errorInfo = ""; 592 for(let attempt = 1; attempt <= maxRetries; attempt++) { 593 try { 594 if(debug)console.log(`Tentativo ${attempt} di esecuzione ASP`); 595 aspResults = await executeASP(currentProgram); 596 if(debug)console.log("Risultati ASP:", aspResults); 597 598 // Se non ci sono errori o warning, usciamo dal ciclo 599 if((!aspResults.Error || aspResults.Error === "")) { 600 if(debug)console.log("Esecuzione ASP completata con successo"); 601 break; 602 } 603 604 // Raccogliamo informazioni su errori e warning 605 errorInfo = ""; 606 if(aspResults.Error && aspResults.Error !== "") { 607 console.error(`ASP Errors (tentativo ${attempt}):`, aspResults.Error); 608 errorInfo += "Errori ASP: " + aspResults.Error; 609 } 610 611 await restartClingo(); 612 // Ultimo tentativo, usciamo dal ciclo 613 if(attempt >= maxRetries ) { 614 if(debug)console.log("Raggiunto numero massimo di tentativi"); 615 break; 616 } 617 618 619 620 // Rigenerazione del programma con le informazioni di errore 621 const programWithError = "%Il programma generato conteneva degli errori:" + errorInfo + 622 "\n\n%Programma errato:\n" + llmResponse.aspQuery +"\n\n%Rigenera il programma in sintassi clingo valida per ottenere informazioni su: " + userInputLowerWithDate; 623 624 const newLlmResponse = await callPresetted(programWithError, "aspGen.prompt"); 625 626 if(newLlmResponse.success) { 627 currentProgram = facts+'\n'+oggi+'\n'+newLlmResponse.aspQuery; 628 } else { 629 break; 630 631 } 632 633 } catch (error) { 634 console.error(`Errore nell'esecuzione ASP (tentativo ${attempt+1}):`, error); 635 removeTypingIndicator(); 636 addMessage("Mi dispiace, ho avuto un problema nell'elaborazione della risposta. Potresti riprovare?", false); 637 status.textContent = "Pronto"; 638 statusLed.className = "status-led green d-none"; 639 document.getElementById("logoDediIcon").style.color = "#0F400F"; 640 updateEyesAndMouth("green"); 641 //return; 642 aspResults = null; // Imposta aspResults a null per uscire dal ciclo 643 } 644 } 645 646 647 648 // Step 3: Interpreta risultati 649 const aspInterpretation = await interpretResults(userInput, aspResults); 650 if(debug)console.log("Interpretazione ASP:", aspInterpretation); 651 // Chiamata al server per elaborare la risposta in linguaggio naturale 652 let naturalLanguageResponse = ""; 653 if ( 654 aspResults && 655 Array.isArray(aspResults.Call) && 656 aspResults.Call.length > 0 && 657 Array.isArray(aspResults.Call[0].Witnesses) && 658 aspResults.Call[0].Witnesses.length > 0 && 659 Array.isArray(aspResults.Call[0].Witnesses[0].Value) && 660 aspResults.Call[0].Witnesses[0].Value.length > 0 661 ) { 662 addMessageToIndicator(frasiStandard.langNatPrompt[Math.floor(Math.random() * frasiStandard.langNatPrompt.length)]); 663 664 naturalLanguageResponse = await callPresetted(aspInterpretation, "langNat.prompt"); 665 } 666 else{ 667 668 669 addMessageToIndicator(frasiStandard.langNatRagPrompt[Math.floor(Math.random() * frasiStandard.langNatRagPrompt.length)]); 670 //fai partire un timer, se in 10 secondi non arriva la risposta aggiungi un nuovo messaggio di interazione, usa una variabile e distruggila dopo aver trovato la naturalLanguageResponse 671 let waitinngRagResponseTimeout = setInterval(() => { 672 addMessageToIndicator(frasiStandard.langNatRagPrompt[Math.floor(Math.random() * frasiStandard.langNatRagPrompt.length)]); 673 674 }, 10000); 675 676 naturalLanguageResponse = await callPresetted(aspInterpretation, "langNatRag.prompt"); 677 clearInterval(waitinngRagResponseTimeout); 678 679 } 680 if(debug)console.log(naturalLanguageResponse); 681 682 removeTypingIndicator(); 683 684 // Mostra solo la risposta naturale all'utente 685 if (naturalLanguageResponse.success) { 686 await addMessageStreamHTMLWithIds(naturalLanguageResponse.aspQuery, false); 687 } else { 688 await addMessageStreamHTMLWithIds("<p>Mi dispiace, non ho trovato informazioni pertinenti alla tua ric
688hiesta. Posso aiutarti con qualcos'altro?</p>", false); 689 690 } 691 692 } catch (error) { 693 console.error("Errore generale:", error); 694 removeTypingIndicator(); 695 addMessage("Mi dispiace, si è verificato un errore. Potresti riprovare tra poco?", false); 696 aspResults = null; 697 } 698 699 // Sblocca input e bottone invio dopo la risposta 700 setChatInputEnabled(true); 701 702 status.textContent = "Pronto"; 703 statusLed.className = "status-led green d-none"; 704 document.getElementById("logoDediIcon").style.color = "#0F400F"; 705 updateEyesAndMouth("green"); 706 }); 707 708 // Invio con Enter 709 document.querySelector('.app-container.dedi #userInput').addEventListener('keypress', (e) => { 710 if (e.key === 'Enter') { 711 document.querySelector('.app-container.dedi .send-button').click(); 712 } 713 }); 714 715 // Gestione revoca consenso 716 const revokeBtn = document.querySelector('.dedi-revoke-consent'); 717 if (revokeBtn) { 718 // Nascondi il bottone se il cookie non esiste 719 if (!getCookie('dediChatConsented')) { 720 revokeBtn.classList.add('d-none'); 721 } 722 revokeBtn.addEventListener('click', function() { 723 eraseCookie('dediChatConsented'); 724 eraseCookie('dediChatAgeGroup'); 725 setChatInputEnabled(false); 726 closeChatModal(); 727 revokeBtn.classList.add('d-none'); 728 }); 729 } 730 }); 731 732 // --- Gestione Consenso Chat --- 733function setCookie(name, value, days) { 734 let expires = ""; 735 if (days) { 736 const date = new Date(); 737 date.setTime(date.getTime() + (days*24*60*60*1000)); 738 expires = "; expires=" + date.toUTCString(); 739 } 740 document.cookie = name + "=" + (value || "") + expires + "; path=/"; 741} 742function getCookie(name) { 743 const nameEQ = name + "="; 744 const ca = document.cookie.split(';'); 745 for(let i=0;i < ca.length;i++) { 746 let c = ca[i]; 747 while (c.charAt(0)==' ') c = c.substring(1,c.length); 748 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); 749 } 750 return null; 751} 752function eraseCookie(name) { 753 document.cookie = name+'=; Max-Age=-99999999; path=/'; 754} 755 756 757 758// Mostra il modal di consenso 759function showConsentModal(onAccept, onDecline) { 760 if (document.getElementById('dediConsentOverlay')) return; 761 let selectHTML = `<select id="dediAgeSelect" class="form-control" style="font-size:14px"> 762 <option value="">Seleziona la tua fascia d'età </option> 763 <option value="under13">Ho meno di 13 anni</option> 764 <option value="13to17">Ho tra 13 e 17 anni</option> 765 <option value="18plus">Ho 18 anni o più</option> 766 </select>`; 767 // Se il dispositivo è un iPhone/iPad/iPod il select deve essere un input radio 768 769 if (iphoneDevice) { 770 selectHTML = ` 771 <div id="dediAgeSelect" style="text-align:left"> 772 <div> 773 <input type="radio" name="dediAgeRadio" id="dediAgeRadio1" value="under13"> 774 <label for="dediAgeRadio1">Ho meno di 13 anni</label> 775 </div> 776 <div> 777 <input type="radio" name="dediAgeRadio" id="dediAgeRadio2" value="13to17"> 778 <label for="dediAgeRadio2">Ho tra 13 e 17 anni</label> 779 </div> 780 <div> 781 <input type="radio" name="dediAgeRadio" id="dediAgeRadio3" value="18plus"> 782 <label for="dediAgeRadio3">Ho 18 anni o più</label> 783 </div> 784 </div> 785 `; 786 } 787
788 const overlay = document.createElement('div'); 789 overlay.id = "dediConsentOverlay"; 790 overlay.style.position = "fixed"; 791 overlay.style.top = "0"; 792 overlay.style.left = "0"; 793 overlay.style.width = "100vw"; 794 overlay.style.height = "100vh"; 795 overlay.style.background = "rgba(0,0,0,0.7)"; 796 overlay.style.zIndex = "99999"; 797 overlay.style.display = "flex"; 798 overlay.style.alignItems = "center"; 799 800 overlay.style.justifyContent = "center"; 801 overlay.innerHTML = ` 802 <div class="card shadow-lg" style="max-width: 400px; font-size:14px; position: relative;"> 803 <button id="dediConsentClose" style="position: absolute; right: 10px; top: 10px; border: none; background: transparent; font-size: 20px; cursor: pointer;">×</button> 804 <div class="card-body text-center"> 805 <h3 class="mb-3">Consenso al trattamento dati</h3> 806 <p class="card-text text-left"> 807 I dati inseriti nella chat saranno inviati a un modello linguistico (LLM) esterno per l'elaborazione.<br> 808 Procedendo acconsenti al trattamento dei tuoi dati personali secondo la nostra 809 <a href='/privacy-policy' target="_blank">Privacy Policy</a>.<br> 810 In Italia se hai meno di 13 anni, non puoi utilizzare la chat mentre se hai tra i 13 e i 17 anni devi essere supervisionato da un genitore o tutore.<br> 811 Se hai 18 anni o più, puoi utilizzare la chat senza restrizioni.<br> Per procedere devi indicare la tua fascia d'età . 812 813 </p> 814 <div class="mb-3"> 815 ${selectHTML} 816 </div> 817 <div id="dediConsentMsg" class="mb-2" style="display:none"></div> 818 <div class="d-flex justify-content-center mt-4" > 819 <button id="dediConsentAccept" style="font-size:14px" class="btn btn-success mr-2" disabled>Acconsento</button> 820 <button id="dediConsentDecline" style="font-size:14px" class="btn btn-secondary">Non acconsento</button> 821 </div> 822 </div> 823 </div> 824 `; 825 826 // Add event listener for the close button 827 828 document.body.appendChild(overlay); 829 830 831 const ageSelect = document.getElementById('dediAgeSelect'); 832 const acceptBtn = document.getElementById('dediConsentAccept'); 833 const msgDiv = document.getElementById('dediConsentMsg'); 834 let ageGroup = ""; 835 document.getElementById('dediConsentClose').onclick = function() { 836 eraseCookie('dediChatConsented'); 837 eraseCookie('dediChatAgeGroup'); 838 document.body.removeChild(overlay); 839 if (onDecline) onDecline(); 840 }; 841 842 ageSelect.addEventListener('change', function() { 843 ageGroup = ageSelect.value; 844 if (iphoneDevice) { 845 // Per iPhone/iPad/iPod, prendi il valore del radio selezionato 846 const selectedRadio = document.querySelector('input[name="dediAgeRadio"]:checked'); 847 ageGroup = selectedRadio ? selectedRadio.value : ""; 848 } 849 msgDiv.style.display = "none"; 850 acceptBtn.disabled = false; 851 if (ageGroup === "under13") { 852 acceptBtn.disabled = true; 853 msgDiv.textContent = "L'utilizzo della chat non è consentito ai minori di 13 anni."; 854 msgDiv.style.display = "block"; 855 msgDiv.style.color = "red"; 856 857 } else if (ageGroup === "13to17") { 858 msgDiv.innerHTML = "Per usare la chat devi essere supervisionato da un genitore o tutore. Cliccando su <b>Acconsento</b> confermi di essere supervisionato."; 859 msgDiv.style.display = "block"; 860 msgDiv.style.color = "black"; 861 862 863 acceptBtn.disabled = false; 864 } else if (ageGroup === "18plus") { 865 msgDiv.style.display = "none"; 866 acceptBtn.disabled = false; 867 } else { 868 acceptBtn.disabled = true; 869 } 870 }); 871 872 acceptBtn.onclick = function() { 873 if (ageGroup === "under13") { 874 // Non dovrebbe mai essere cliccabile, ma per sicurezza 875 msgDiv.textContent = "L'utilizzo della chat non è consentito ai minori di 13 anni."; 876 msgDiv.style.display = "block"; 877 return; 878 } 879
880 setCookie('dediChatConsented', 'true', 180); 881 setCookie('dediChatAgeGroup', ageGroup, 180); 882 document.body.removeChild(overlay); 883 const revokeBtn = document.querySelector('.dedi-revoke-consent'); 884 if (revokeBtn) { 885 revokeBtn.classList.remove('d-none'); 886 } 887 if (onAccept) onAccept(); 888 }; 889 document.getElementById('dediConsentDecline').onclick = function() { 890 eraseCookie('dediChatConsented'); 891 eraseCookie('dediChatAgeGroup'); 892 document.body.removeChild(overlay); 893 if (onDecline) onDecline(); 894 }; 895} 896 897// Disabilita/abilita input chat 898function setChatInputEnabled(enabled) { 899 var input = document.getElementById('userInput'); 900 var sendBtn = document.getElementById('sendButton'); 901 if (input) input.disabled = !enabled; 902 if (sendBtn) sendBtn.disabled = !enabled; 903} 904 905// Chiudi il modale della chat (Bootstrap) 906function closeChatModal() { 907 $('#chatModal').modal('hide'); 908} 909 910// Gestione consenso all'apertura della chat 911document.addEventListener('DOMContentLoaded', function() { 912 var openBtn = document.querySelector('.open-chat-button'); 913 if (openBtn) { 914 openBtn.addEventListener('click', function(e) { 915 setTimeout(function() { 916 if (getCookie('dediChatConsented') === 'true') { 917 setChatInputEnabled(true); 918 } else { 919 setChatInputEnabled(false); 920 showConsentModal( 921 function onAccept() { 922 setChatInputEnabled(true); 923 }, 924 function onDecline() { 925 setChatInputEnabled(false); 926 closeChatModal(); 927 } 928 ); 929 } 930 }, 300); // attende che il modale sia visibile 931 }); 932 } 933 // All'avvio, input chat sempre disabilitato 934 setChatInputEnabled(false); 935});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.