Download app.py from esab/APP_SPACE: direct link, hf CLI and curl.
- Browser
- Download file 10 kB
-
https://huggingface.co/spaces/esab/APP_SPACE/resolve/main/app.py
- Command line
-
hf download hf://spaces/esab/APP_SPACE/app.py
-
curl -L -o app.py https://huggingface.co/spaces/esab/APP_SPACE/resolve/main/app.py
10 kB
| import os | |
| import re | |
| import asyncio | |
| import gradio as gr | |
| from google import genai | |
| from google.genai import types | |
| from langdetect import detect, DetectorFactory | |
| # Make language detection deterministic across restarts | |
| DetectorFactory.seed = 0 | |
| api_key = os.getenv("GEMINI_API") | |
| if not api_key: | |
| raise RuntimeError( | |
| "Falta la variable de entorno GEMINI_API. " | |
| "Configúrala como secreto del Space (Settings > Variables and secrets)." | |
| ) | |
| client = genai.Client(api_key=api_key) | |
| MODEL_NAME = os.getenv("GEMINI_MODEL", "gemini-2.5-flash") | |
| GENERATION_CONFIG = { | |
| "temperature": 0.7, | |
| "top_p": 0.9, | |
| "top_k": 30, | |
| "max_output_tokens": 2500, | |
| } | |
| # La detección automática de idioma no es fiable en frases cortas sin acentos, | |
| # así que la instrucción pide además responder en el idioma que use el estudiante. | |
| LANGUAGE_RULE_ES = ( | |
| "Responde siempre en el mismo idioma que utilice el estudiante " | |
| "(castellano o catalán), aunque esta instrucción esté en otro idioma." | |
| ) | |
| LANGUAGE_RULE_CA = ( | |
| "Respon sempre en el mateix idioma que faci servir l'estudiant " | |
| "(català o castellà), encara que aquesta instrucció estigui en un altre idioma." | |
| ) | |
| # Rol pedagógico: acompañar, no resolver. El objetivo es que el estudiante | |
| # llegue por sí mismo a la solución, no entregarle el resultado hecho. | |
| PEDAGOGY_ES = """Eres un tutor de estadística que acompaña a estudiantes de grado. \ | |
| Tu objetivo es que el estudiante aprenda a resolver por sí mismo. No eres un resolvedor de ejercicios. | |
| Antes de responder, distingue SIEMPRE entre dos situaciones: | |
| (a) PREGUNTA CONCEPTUAL O TEÓRICA ("qué es", "qué diferencia hay entre", "cómo se interpreta", \ | |
| "cuándo se usa", "por qué"). Respóndela directamente, a fondo y con ejemplos. NO le preguntes \ | |
| primero qué ha intentado: sería frustrante y no tiene sentido. Puedes terminar con una pregunta \ | |
| breve para comprobar que lo ha entendido, pero solo después de haber explicado. | |
| (b) EJERCICIO CONCRETO que el estudiante debe resolver o entregar (trae datos, un enunciado, o pide \ | |
| "calcula", "resuélveme", "cuánto da"). Aquí es donde NO das el resultado: guías. | |
| Cómo debes actuar en el caso (b): | |
| - Cuando el estudiante plantee un ejercicio concreto, NO lo resuelvas. Empieza preguntándole qué ha \ | |
| intentado y qué concepto cree que se aplica. | |
| - Guía con pistas graduales: primero una orientación general; si sigue atascado, una pista más concreta; \ | |
| y solo después el siguiente paso del razonamiento. | |
| - Explica los conceptos con toda la profundidad que haga falta. Explicar teoría, definiciones e \ | |
| interpretaciones es tu trabajo principal y ahí debes ser generoso. | |
| - Lo que no debes dar es el resultado final del ejercicio concreto que le han mandado. | |
| - Si necesitas ilustrar un método, usa un ejemplo análogo con datos distintos a los del enunciado. | |
| - Si el estudiante propone una respuesta, revísala: dile si el razonamiento es correcto o dónde falla, \ | |
| y por qué. | |
| - Si insiste en que le des la solución, recuérdale con amabilidad que tu papel es ayudarle a llegar él \ | |
| mismo, y ofrécele la siguiente pista. | |
| - Haz las preguntas de una en una y mantén las respuestas breves. No abrumes. | |
| Sobre el código: | |
| - Todo el código debe estar en R. | |
| - Puedes mostrar funciones y sintaxis, pero aplicadas a un ejemplo propio, no al ejercicio del estudiante. | |
| Límites: | |
| - Trata únicamente temas de estadística básica. Si preguntan otra cosa, redirige con amabilidad.""" | |
| PEDAGOGY_CA = """Ets un tutor d'estadística que acompanya estudiants de grau. \ | |
| El teu objectiu és que l'estudiant aprengui a resoldre pel seu compte. No ets un resoledor d'exercicis. | |
| Abans de respondre, distingeix SEMPRE entre dues situacions: | |
| (a) PREGUNTA CONCEPTUAL O TEÒRICA ("què és", "quina diferència hi ha entre", "com s'interpreta", \ | |
| "quan es fa servir", "per què"). Respon-la directament, a fons i amb exemples. NO li preguntis \ | |
| primer què ha provat: seria frustrant i no té sentit. Pots acabar amb una pregunta breu per \ | |
| comprovar que ho ha entès, però només després d'haver explicat. | |
| (b) EXERCICI CONCRET que l'estudiant ha de resoldre o entregar (porta dades, un enunciat, o demana \ | |
| "calcula", "resol-me", "quant dona"). Aquí és on NO dones el resultat: guies. | |
| Com has d'actuar en el cas (b): | |
| - Quan l'estudiant plantegi un exercici concret, NO el resolguis. Comença preguntant-li què ha provat \ | |
| i quin concepte creu que s'hi aplica. | |
| - Guia amb pistes graduals: primer una orientació general; si continua encallat, una pista més concreta; \ | |
| i només després el pas següent del raonament. | |
| - Explica els conceptes amb tota la profunditat que calgui. Explicar teoria, definicions i \ | |
| interpretacions és la teva feina principal i aquí has de ser generós. | |
| - El que no has de donar és el resultat final de l'exercici concret que li han manat. | |
| - Si necessites il·lustrar un mètode, fes servir un exemple anàleg amb dades diferents de les de \ | |
| l'enunciat. | |
| - Si l'estudiant proposa una resposta, revisa-la: digues-li si el raonament és correcte o on falla, \ | |
| i per què. | |
| - Si insisteix que li donis la solució, recorda-li amablement que el teu paper és ajudar-lo a arribar-hi \ | |
| ell mateix, i ofereix-li la pista següent. | |
| - Fes les preguntes d'una en una i mantén les respostes breus. No aclaparis. | |
| Sobre el codi: | |
| - Tot el codi ha de ser en R. | |
| - Pots mostrar funcions i sintaxi, però aplicades a un exemple propi, no a l'exercici de l'estudiant. | |
| Límits: | |
| - Tracta únicament temes d'estadística bàsica. Si pregunten una altra cosa, redirigeix amablement.""" | |
| instructions = { | |
| "es": PEDAGOGY_ES + "\n\n" + LANGUAGE_RULE_ES, | |
| "ca": PEDAGOGY_CA + "\n\n" + LANGUAGE_RULE_CA, | |
| } | |
| INAPPROPRIATE_PATTERNS = [ | |
| re.compile(r"\b(joder|maleducado)\b", re.IGNORECASE), | |
| ] | |
| def detect_language(user_input): | |
| try: | |
| lang = detect(user_input) | |
| except Exception: | |
| return "es" | |
| return lang if lang in instructions else "es" | |
| def contains_inappropriate_language(text): | |
| return any(pattern.search(text) for pattern in INAPPROPRIATE_PATTERNS) | |
| def create_chat_session(system_instruction): | |
| return client.chats.create( | |
| model=MODEL_NAME, | |
| config=types.GenerateContentConfig( | |
| system_instruction=system_instruction, | |
| **GENERATION_CONFIG, | |
| ), | |
| ) | |
| async def chatbot_async(user_input, history, chat_session): | |
| user_input = (user_input or "").strip() | |
| if not user_input: | |
| return history, history, chat_session, "" | |
| history = history + [{"role": "user", "content": user_input}] | |
| if contains_inappropriate_language(user_input): | |
| warning = ( | |
| "El lenguaje inapropiado no está permitido. " | |
| "Por favor, formula tu pregunta de manera respetuosa." | |
| ) | |
| history.append({"role": "assistant", "content": warning}) | |
| return history, history, chat_session, "" | |
| try: | |
| if chat_session is None: | |
| lang = detect_language(user_input) | |
| chat_session = create_chat_session(instructions[lang]) | |
| response = await asyncio.to_thread(chat_session.send_message, user_input) | |
| reply = (response.text or "").strip() or "No he podido generar una respuesta." | |
| except Exception as exc: | |
| reply = f"Ha ocurrido un error: {exc}" | |
| history.append({"role": "assistant", "content": reply}) | |
| return history, history, chat_session, "" | |
| def reset_chat(): | |
| return [], [], None, "" | |
| # Paleta alineada con Atenea / Moodle Boost (azul primario #0f6cbf) | |
| UPC_BLUE = gr.themes.Color( | |
| c50="#e9f2fa", c100="#cfe2f3", c200="#a4c8e8", c300="#78addc", | |
| c400="#4d93d1", c500="#0f6cbf", c600="#0d5da4", c700="#0a4a83", | |
| c800="#083762", c900="#052440", c950="#03182b", | |
| ) | |
| theme = gr.themes.Soft( | |
| primary_hue=UPC_BLUE, | |
| secondary_hue=UPC_BLUE, | |
| neutral_hue="slate", | |
| font=[gr.themes.GoogleFont("Open Sans"), "system-ui", "sans-serif"], | |
| ).set( | |
| body_background_fill="#ffffff", | |
| block_background_fill="#ffffff", | |
| block_border_width="1px", | |
| block_shadow="none", | |
| button_primary_background_fill="#0f6cbf", | |
| button_primary_background_fill_hover="#0d5da4", | |
| button_primary_text_color="#ffffff", | |
| ) | |
| # Oculta el pie de Gradio y ajusta el marco para que no compita con la pagina de Atenea | |
| CUSTOM_CSS = """ | |
| footer { display: none !important; } | |
| .gradio-container { max-width: 100% !important; padding: 8px !important; } | |
| #chat-header { margin-bottom: 4px; } | |
| #chat-header h2 { color: #0f6cbf; font-size: 1.15rem; margin: 0 0 2px 0; } | |
| #chat-header p { color: #555; font-size: 0.9rem; margin: 0; } | |
| """ | |
| with gr.Blocks(title="Tutor d'Estadística") as demo: | |
| gr.HTML( | |
| "<div id='chat-header'>" | |
| "<h2>Tutor virtual d'Estadística</h2>" | |
| "<p>T'ajudo a arribar tu mateix a la solució: no et dono l'exercici resolt. Pregunta en català o en castellano. El codi, en R.</p>" | |
| "</div>" | |
| ) | |
| chatbot_interface = gr.Chatbot( | |
| height=430, | |
| show_label=False, | |
| buttons=["copy"], | |
| placeholder="<em>Explica'm qu\u00e8 has provat i on t'has encallat.</em>", | |
| ) | |
| state = gr.State([]) # chat history in messages format | |
| chat_session = gr.State(None) # google-genai chat session | |
| with gr.Row(): | |
| txt = gr.Textbox( | |
| show_label=False, | |
| placeholder="Escribe tu pregunta aquí…", | |
| scale=8, | |
| autofocus=True, | |
| container=False, | |
| ) | |
| send_button = gr.Button("Enviar", variant="primary", scale=1, min_width=110) | |
| clear_button = gr.Button("Netejar", variant="secondary", scale=1, min_width=110) | |
| inputs = [txt, state, chat_session] | |
| outputs = [chatbot_interface, state, chat_session, txt] | |
| txt.submit(chatbot_async, inputs, outputs) | |
| send_button.click(chatbot_async, inputs, outputs) | |
| clear_button.click(reset_chat, None, outputs) | |
| if __name__ == "__main__": | |
| demo.launch(theme=theme, css=CUSTOM_CSS, footer_links=[]) | |