Download app.py from SFL046/Safe: direct link, hf CLI and curl.
- Browser
- Download file 8.07 kB
-
https://huggingface.co/spaces/SFL046/Safe/resolve/main/app.py
- Command line
-
hf download hf://spaces/SFL046/Safe/app.py
-
curl -L -o app.py https://huggingface.co/spaces/SFL046/Safe/resolve/main/app.py
8.07 kB
| import os | |
| import json | |
| import tempfile | |
| import warnings | |
| import gradio as gr | |
| from openai import OpenAI | |
| from datetime import datetime | |
| # ========================================== | |
| # SUPRESIÓN DE LOGS COSMÉTICOS (PYTHON 3.13) | |
| # ========================================== | |
| warnings.filterwarnings("ignore", category=RuntimeWarning, module="asyncio") | |
| warnings.filterwarnings("ignore", message=".*Invalid file descriptor.*") | |
| # ========================================== | |
| # CONFIGURACIÓN DE API (DASHSCOPE INTL) | |
| # ========================================== | |
| BASE_URL = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1" | |
| MODEL_NAME = "qwen-plus" | |
| QWEN_API_KEY = os.environ.get("QWEN_API_KEY") | |
| if not QWEN_API_KEY: | |
| raise ValueError( | |
| "⚠️ ERROR CRÍTICO: QWEN_API_KEY no encontrada.\n" | |
| "Por favor, añade tu clave en Settings > Repository secrets del Space." | |
| ) | |
| client = OpenAI( | |
| api_key=QWEN_API_KEY, | |
| base_url=BASE_URL | |
| ) | |
| SYSTEM_PROMPT = """Eres un asistente experto en programación. | |
| Tus capacidades incluyen: explicar código, depurar errores, generar ejemplos y optimizar funciones. | |
| REGLAS ESTRICTAS: | |
| 1. NO ejecutes código localmente. | |
| Solo analízalo o genéralo. | |
| 2. Sé conciso, técnico y usa bloques de código con sintaxis resaltada. | |
| 3. Si el usuario sube un documento, analízalo exclusivamente en memoria. | |
| 4. Respeta la privacidad: no solicites ni almacenes datos personales.""" | |
| # ========================================== | |
| # LÓGICA DEL BOT | |
| # ========================================== | |
| def chat_with_qwen(history, uploaded_file): | |
| messages = [{"role": "system", "content": SYSTEM_PROMPT}] | |
| file_context = "" | |
| if uploaded_file is not None: | |
| try: | |
| with open(uploaded_file.name, "r", encoding="utf-8") as f: | |
| content = f.read() | |
| file_context = f"\n\n[CONTEXTO DEL ARCHIVO SUBIDO]:\n{content[:15000]}" | |
| except UnicodeDecodeError: | |
| file_context = "\n\n[ERROR: El archivo subido no es texto plano legible.]" | |
| except Exception as e: | |
| file_context = f"\n\n[ERROR LEYENDO ARCHIVO: {str(e)}]" | |
| for i, msg in enumerate(history): | |
| content = msg["content"] | |
| if i == len(history) - 1 and msg["role"] == "user": | |
| content += file_context | |
| messages.append({"role": msg["role"], "content": content}) | |
| try: | |
| response = client.chat.completions.create( | |
| model=MODEL_NAME, | |
| messages=messages, | |
| temperature=0.7, | |
| max_tokens=2048 | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| return f"❌ Error en API de Qwen: {str(e)}" | |
| def export_conversation(history): | |
| if not history: | |
| gr.Warning("No hay conversación para exportar.") | |
| return gr.update(visible=False) | |
| export_data = { | |
| "metadata": { | |
| "exported_at": datetime.now().isoformat(), | |
| "bot_type": "Qwen Programming Assistant", | |
| "privacy_note": "This file was generated in-memory and never stored on server." | |
| }, | |
| "conversation": history | |
| } | |
| json_str = json.dumps(export_data, indent=2, ensure_ascii=False) | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| with tempfile.NamedTemporaryFile( | |
| mode='w', | |
| suffix=f'_{timestamp}.json', | |
| prefix='qwen_session_', | |
| delete=False, | |
| encoding='utf-8' | |
| ) as f: | |
| f.write(json_str) | |
| return gr.update(value=f.name, visible=True) | |
| def clear_chat(): | |
| return [], None, None, gr.update(visible=False) | |
| # ========================================== | |
| # INTERFAZ GRADIO (PALETA DE COLORES "SAFE") | |
| # ========================================== | |
| CUSTOM_CSS = """ | |
| /* Fondo general de la aplicación */ | |
| body, .gradio-container { | |
| background-color: #0b0f17 !important; | |
| } | |
| /* Banner de Privacidad Estilo Safe CyberSecurity */ | |
| .privacy-banner { | |
| background: #111827; | |
| color: #9ca3af; | |
| padding: 16px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| font-size: 0.95em; | |
| text-align: center; | |
| border: 1px solid #1f2937; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); | |
| } | |
| .privacy-banner strong { | |
| color: #10b981; /* Verde esmeralda de acento */ | |
| } | |
| /* Burbujas del Chatbot */ | |
| .gradio-container .message.user { | |
| background-color: #1f2937 !important; | |
| color: #f9fafb !important; | |
| border: 1px solid #374151 !important; | |
| } | |
| .gradio-container .message.bot { | |
| background-color: #111827 !important; | |
| color: #e5e7eb !important; | |
| border: 1px solid #1f2937 !important; | |
| } | |
| /* Bloques de código embebidos */ | |
| .gradio-container pre, .gradio-container code { | |
| background-color: #030712 !important; | |
| border: 1px solid #1f2937 !important; | |
| } | |
| /* Cajas de texto y Textareas */ | |
| textarea { | |
| background-color: #111827 !important; | |
| color: #f9fafb !important; | |
| border: 1px solid #1f2937 !important; | |
| } | |
| textarea:focus { | |
| border-color: #10b981 !important; | |
| } | |
| """ | |
| # Configuración limpia del tema preestablecido (Emerald/Zinc) | |
| safe_theme = gr.themes.Soft( | |
| primary_hue="emerald", # Verde esmeralda para los botones principales y focos | |
| neutral_hue="zinc", # Escala de grises oscuros profundos y limpios | |
| ).set( | |
| body_background_fill="#0b0f17", | |
| block_background_fill="#111827", | |
| block_border_color="#1f2937", | |
| button_primary_background_fill="#10b981", | |
| button_primary_background_fill_hover="#059669", | |
| button_primary_text_color="#ffffff" | |
| ) | |
| with gr.Blocks(title="Safe - isolated IA assistent", theme=safe_theme, css=CUSTOM_CSS) as demo: | |
| gr.HTML(""" | |
| <div class="privacy-banner"> | |
| 🔒 <strong>Sesión Efímera:</strong> Esta conversación existe SOLO en memoria RAM. | |
| Al cerrar/recargar la pestaña, todos los datos se eliminan permanentemente. | |
| No se usa base de datos ni almacenamiento persistente. | |
| </div> | |
| """) | |
| chatbot = gr.Chatbot(height=450, label="Safe - isolated IA assistent") | |
| msg_input = gr.Textbox( | |
| placeholder="Pega tu código, describe un error o pide una explicación...", | |
| lines=2, | |
| show_label=False | |
| ) | |
| send_btn = gr.Button("💬 Enviar", variant="primary", size="lg") | |
| file_upload = gr.File( | |
| label="📎 Subir documento/código (Opcional)", | |
| file_types=[".py", ".js", ".ts", ".java", ".cpp", ".txt", ".md", ".json"], | |
| height=110 | |
| ) | |
| with gr.Row(): | |
| clear_btn = gr.Button("🗑️ Limpiar Conversación", variant="stop") | |
| export_btn = gr.Button("📥 Exportar JSON", variant="secondary") | |
| download_file = gr.File(label="📥 Descargar sesión exportada", visible=False) | |
| # === EVENT HANDLERS === | |
| def user_submit(message, history, file): | |
| if not message.strip() and file is None: | |
| return "", history, file | |
| display_msg = message if message.strip() else f"[Archivo subido: {file.name}]" | |
| history = history + [{"role": "user", "content": display_msg}] | |
| return "", history, file | |
| def bot_respond(history, file): | |
| if not history: | |
| return history | |
| response = chat_with_qwen(history, file) | |
| history = history + [{"role": "assistant", "content": response}] | |
| return history | |
| send_btn.click( | |
| user_submit, [msg_input, chatbot, file_upload], [msg_input, chatbot, file_upload] | |
| ).then( | |
| bot_respond, [chatbot, file_upload], chatbot | |
| ) | |
| msg_input.submit( | |
| user_submit, [msg_input, chatbot, file_upload], [msg_input, chatbot, file_upload] | |
| ).then( | |
| bot_respond, [chatbot, file_upload], chatbot | |
| ) | |
| clear_btn.click( | |
| clear_chat, | |
| outputs=[chatbot, msg_input, file_upload, download_file] | |
| ) | |
| export_btn.click( | |
| export_conversation, | |
| inputs=[chatbot], | |
| outputs=[download_file] | |
| ) | |
| # ========================================== | |
| # PUNTO DE ENTRADA | |
| # ========================================== | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| share=False | |
| ) |