PageSourceSearch

https://calabriadigital.it/o/digital.desme.dedi/assets/javascript/main.js

js calabriadigital.it collected 2026-10-03 02:51:49 UTC 45,277 bytes, 935 lines download raw bytes

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;">&times;</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.