- Front end
Per la creació del witget hem utilitzat un codi d’html amb l’ajuda de la IA Gemini.
Justifiació:
En el mateix codi d’html està totes les justificacions del perquè en cada funció.
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<style>
/* 1. ESTILS VISUALS (CSS) */
/* Definim l'aparença del xat per fer-lo modern i intuïtiu. */
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 10px; background-color: transparent; }
/* Contenidor principal amb vora i cantons arrodonits */
#chat-container { border: 2px solid #2c3e50; border-radius: 10px; max-width: 450px; background: #fff; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
/* Zona on apareixen els missatges amb scroll automàtic */
#messages { height: 250px; overflow-y: auto; padding: 10px; background: #f4f7f6; display: flex; flex-direction: column; gap: 8px; }
/* Disseny de les bombolles de text */
.msg { padding: 8px 12px; border-radius: 15px; font-size: 14px; max-width: 80%; line-height: 1.4; }
/* Estil per a l'usuari (dreta i color blau) */
.user { background: #d1e7ff; align-self: flex-end; color: #004085; border-bottom-right-radius: 2px; }
/* Estil per al bot (esquerra i color verd) */
.bot { background: #d4edda; align-self: flex-start; color: #155724; border-bottom-left-radius: 2px; }
/* Estil per als missatges d'error */
.error { background: #f8d7da; color: #721c24; align-self: center; font-size: 12px; border-radius: 5px; }
/* Àrea d'entrada de text */
#input-area { display: flex; border-top: 1px solid #ddd; padding: 10px; background: #fff; }
input { flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 5px; outline: none; }
button { margin-left: 5px; padding: 8px 15px; background: #27ae60; color: white; border: none; border-radius: 5px; cursor: pointer; transition: 0.3s; }
button:hover { background: #219150; }
</style>
</head>
<body>
<div id="chat-container">
<div id="messages">
<div class="msg bot">Hola! Soc l'assistent virtual. En què et puc ajudar avui?</div>
</div>
<div id="input-area">
<input type="text" id="userInput" placeholder="Escriu la teva pregunta...">
<button onclick="enviar()">Enviar</button>
</div>
</div>
<script>
/* 2. LÒGICA DE FUNCIONAMENT (JAVASCRIPT) */
async function enviar() {
const input = document.getElementById('userInput');
const box = document.getElementById('messages');
const msg = input.value.trim();
// Validació: Si no hi ha text, no fem res
if(!msg) return;
// AFEGIR MISSATGE DE L'USUARI A LA PANTALLA
box.innerHTML += `<div class="msg user">${msg}</div>`;
input.value = ''; // Netegem l'input
box.scrollTop = box.scrollHeight; // Fem scroll cap avall
// 3. CONNEXIÓ AMB EL BACKEND (Google Colab via Ngrok)
// Aquesta URL canvia cada vegada que reinicies el Colab
const URL_BACKEND = "https://micki-untellable-rayford.ngrok-free.dev/ask";
try {
// Fem la petició HTTP POST al nostre servidor Flask
const res = await fetch(URL_BACKEND, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Aquesta capçalera és necessària per saltar la pàgina d'avís de Ngrok
'ngrok-skip-browser-warning': 'true'
},
// Enviem el missatge de l'usuari convertit a text JSON
body: JSON.stringify({ message: msg })
});
// Processem la resposta del servidor
const data = await res.json();
// Control per consola per a depuració (debug)
console.log("Resposta del servidor:", data);
if (data.reply) {
// Si la IA respon correctament, mostrem la bombolla del bot
box.innerHTML += `<div class="msg bot">${data.reply}</div>`;
} else {
// Si el servidor respon però hi ha un error de quota o configuració
box.innerHTML += `<div class="msg error">⚠️ Error: ${data.error || 'Resposta inesperada'}</div>`;
}
} catch (e) {
// Si no es pot connectar (servidor parat o URL incorrecta)
box.innerHTML += `<div class="msg error">⚠️ No s'ha pogut connectar amb la IA. Revisa si el servidor Colab està actiu.</div>`;
}
// Tornem a fer scroll per veure l'última resposta
box.scrollTop = box.scrollHeight;
}
// Permetre enviar el missatge prement la tecla "Enter"
document.getElementById('userInput').addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
enviar();
}
});
</script>
</body>
</html>
2. Gestió de Dades Estructurades (JSON)
En lloc d’utilitzar text pla, tota la informació de l’esdeveniment (IPs, competicions, normativa Ecotech) està organitzada en un objecte JSON. Això permet que el sistema sigui escalable i que la IA pugui “llegir” la base de dades del projecte de manera precisa i jeràrquica.

3. Interfície d’Usuari Integrada (Witget)
Per fer el witget he implementat com un element encastat i fix. S’han utilitzat tons verds i grisos neutres per transmetre una imatge tecnològica i neta, alineada amb l’esdeveniment.
Tipografia i Llegibilitat: S’ha escollit una font sans-serif moderna per facilitar la lectura ràpida en pantalles, amb bombolles de xat diferenciades per colors (blau per a l’usuari i gris/verd per al bot) que fan la conversa visualment intuïtiva.
Disseny Minimalista: La interfície evita elements innecessaris, centrant l’atenció en el contingut del missatge i oferint una interacció directa i sense friccions des del mateix cos de la web.

4. L’Endpoint de Flask i el retorn de dades (Python)
Això es troba al teu fitxer de Colab. És la part que “escolta” les preguntes i “retorna” les respostes.
- L’Endpoint: Es defineix amb
@app.route('/ask', methods=['POST']). És la “porta” d’entrada. - El retorn de dades: Es fa amb
return jsonify({"reply": texto_ia.strip()}). Això transforma la resposta de la IA en un format que la web entén.


5. IA amb “Grounding”
Per evitar que la IA inventi respostes (al·lucinacions), el model Gemini 2.5 Flash utilitza el fitxer JSON com a única font de veritat. El bot està programat per ser un assistent amable i especialitzat que només dona informació oficial confirmada pels organitzadors.

6. El Túnel de ngrok (Python)
Això es troba al final del teu codi de Colab. És el que permet que la teva web de Google Sites (que està a internet) pugui “parlar” amb el teu ordinador (que és privat).