import gradio as gr import json import zipfile import io import os from datetime import datetime from dotenv import load_dotenv import requests from bs4 import BeautifulSoup # Load environment variables from .env file load_dotenv() # Template for generated space app (based on mvp_simple.py) SPACE_TEMPLATE = '''import gradio as gr import os import requests import json from bs4 import BeautifulSoup # Configuration SPACE_NAME = "{name}" SPACE_DESCRIPTION = "{description}" SYSTEM_PROMPT = """{system_prompt}""" MODEL = "{model}" GROUNDING_URLS = {grounding_urls} # Get API key from environment - customizable variable name API_KEY = os.environ.get("{api_key_var}") def fetch_url_content(url): """Fetch and extract text content from a URL""" try: response = requests.get(url, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.content, 'html.parser') # Remove script and style elements for script in soup(["script", "style"]): script.decompose() # Get text content text = soup.get_text() # Clean up whitespace lines = (line.strip() for line in text.splitlines()) chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) text = ' '.join(chunk for chunk in chunks if chunk) # Truncate to ~4000 characters if len(text) > 4000: text = text[:4000] + "..." return text except Exception as e: return f"Error fetching {{url}}: {{str(e)}}" def get_grounding_context(): """Fetch context from grounding URLs""" if not GROUNDING_URLS: return "" context_parts = [] for i, url in enumerate(GROUNDING_URLS, 1): if url.strip(): content = fetch_url_content(url.strip()) context_parts.append(f"Context from URL {{i}} ({{url}}):\\n{{content}}") if context_parts: return "\\n\\n" + "\\n\\n".join(context_parts) + "\\n\\n" return "" def generate_response(message, history): """Generate response using OpenRouter API""" if not API_KEY: return "Please set your {api_key_var} in the Space settings." # Get grounding context grounding_context = get_grounding_context() # Build enhanced system prompt with grounding context enhanced_system_prompt = SYSTEM_PROMPT + grounding_context # Build messages array for the API messages = [{{"role": "system", "content": enhanced_system_prompt}}] # Add conversation history - compatible with Gradio 5.x format for chat in history: if isinstance(chat, dict): # New format: {{"role": "user", "content": "..."}} or {{"role": "assistant", "content": "..."}} messages.append(chat) else: # Legacy format: ("user msg", "bot msg") user_msg, bot_msg = chat messages.append({{"role": "user", "content": user_msg}}) if bot_msg: messages.append({{"role": "assistant", "content": bot_msg}}) # Add current message messages.append({{"role": "user", "content": message}}) # Make API request try: response = requests.post( url="https://openrouter.ai/api/v1/chat/completions", headers={{ "Authorization": f"Bearer {{API_KEY}}", "Content-Type": "application/json" }}, json={{ "model": MODEL, "messages": messages, "temperature": {temperature}, "max_tokens": {max_tokens} }} ) if response.status_code == 200: return response.json()['choices'][0]['message']['content'] else: return f"Error: {{response.status_code}} - {{response.text}}" except Exception as e: return f"Error: {{str(e)}}" # Create simple Gradio interface using ChatInterface demo = gr.ChatInterface( fn=generate_response, title=SPACE_NAME, description=SPACE_DESCRIPTION, examples={examples} ) if __name__ == "__main__": demo.launch() ''' # Available models MODELS = [ "google/gemma-2-27b-it", "mistralai/mixtral-8x7b-instruct", "meta-llama/llama-3.1-70b-instruct", "anthropic/claude-3-haiku", "nvidia/nemotron-4-340b-instruct" ] def fetch_url_content(url): """Fetch and extract text content from a URL""" try: response = requests.get(url, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.content, 'html.parser') # Remove script and style elements for script in soup(["script", "style"]): script.decompose() # Get text content text = soup.get_text() # Clean up whitespace lines = (line.strip() for line in text.splitlines()) chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) text = ' '.join(chunk for chunk in chunks if chunk) # Truncate to ~4000 characters if len(text) > 4000: text = text[:4000] + "..." return text except Exception as e: return f"Error fetching {url}: {str(e)}" def get_grounding_context(urls): """Fetch context from grounding URLs""" if not urls: return "" context_parts = [] for i, url in enumerate(urls, 1): if url and url.strip(): content = fetch_url_content(url.strip()) context_parts.append(f"Context from URL {i} ({url}):\n{content}") if context_parts: return "\n\n" + "\n\n".join(context_parts) + "\n\n" return "" def create_readme(config): """Generate README with deployment instructions""" return f"""--- title: {config['name']} emoji: 🤖 colorFrom: blue colorTo: red sdk: gradio sdk_version: 4.32.0 app_file: app.py pinned: false --- # {config['name']} {config['description']} ## Quick Deploy to HuggingFace Spaces ### Step 1: Create the Space 1. Go to https://huggingface.co/spaces 2. Click "Create new Space" 3. Choose a name for your Space 4. Select **Gradio** as the SDK 5. Set visibility (Public/Private) 6. Click "Create Space" ### Step 2: Upload Files 1. In your new Space, click "Files" tab 2. Upload these files from the zip: - `app.py` - `requirements.txt` 3. Wait for "Building" to complete ### Step 3: Add API Key 1. Go to Settings (gear icon) 2. Click "Variables and secrets" 3. Click "New secret" 4. Name: `{config['api_key_var']}` 5. Value: Your OpenRouter API key 6. Click "Add" ### Step 4: Get Your API Key 1. Go to https://openrouter.ai/keys 2. Sign up/login if needed 3. Click "Create Key" 4. Copy the key (starts with `sk-or-`) ### Step 5: Test Your Space - Go back to "App" tab - Your Space should be running! - Try the example prompts or ask a question ## Configuration - **Model**: {config['model']} - **Temperature**: {config['temperature']} - **Max Tokens**: {config['max_tokens']} - **API Key Variable**: {config['api_key_var']} ## Cost Information OpenRouter charges per token used: - Gemma 2 9B: ~$0.20 per million tokens - Claude Haiku: ~$0.25 per million tokens - GPT-4o-mini: ~$0.60 per million tokens Typical conversation: ~300 tokens (cost: $0.00006 - $0.0018) Check current pricing: https://openrouter.ai/models ## Customization To modify your Space: 1. Edit `app.py` in your Space 2. Update configuration variables at the top 3. Changes deploy automatically ## Troubleshooting - **"Please set your {config['api_key_var']}"**: Add the secret in Space settings - **Error 401**: Invalid API key or no credits - **Error 429**: Rate limit - wait and try again - **Build failed**: Check requirements.txt formatting ## More Help - HuggingFace Spaces: https://huggingface.co/docs/hub/spaces - OpenRouter Docs: https://openrouter.ai/docs - Gradio Docs: https://gradio.app/docs --- Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} with Chat U/I Helper """ def create_requirements(): """Generate requirements.txt""" return "gradio==4.44.1\nrequests==2.32.3\nbeautifulsoup4==4.12.3" def generate_zip(name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, url1="", url2="", url3="", url4=""): """Generate deployable zip file""" # Process examples if examples_text and examples_text.strip(): examples_list = [ex.strip() for ex in examples_text.split('\n') if ex.strip()] examples_json = json.dumps(examples_list) else: examples_json = json.dumps([ "Hello! How can you help me?", "Tell me something interesting", "What can you do?" ]) # Process grounding URLs grounding_urls = [] for url in [url1, url2, url3, url4]: if url and url.strip(): grounding_urls.append(url.strip()) # Create config config = { 'name': name, 'description': description, 'system_prompt': system_prompt, 'model': model, 'api_key_var': api_key_var, 'temperature': temperature, 'max_tokens': int(max_tokens), 'examples': examples_json, 'grounding_urls': json.dumps(grounding_urls) } # Generate files app_content = SPACE_TEMPLATE.format(**config) readme_content = create_readme(config) requirements_content = create_requirements() # Create zip file with clean naming filename = f"{name.lower().replace(' ', '_').replace('-', '_')}.zip" # Create zip in memory and save to disk zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: zip_file.writestr('app.py', app_content) zip_file.writestr('requirements.txt', requirements_content) zip_file.writestr('README.md', readme_content) zip_file.writestr('config.json', json.dumps(config, indent=2)) # Write zip to file zip_buffer.seek(0) with open(filename, 'wb') as f: f.write(zip_buffer.getvalue()) return filename # Define callback functions outside the interface def on_generate(name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, url1, url2, url3, url4): if not name or not name.strip(): return gr.update(value="Error: Please provide a Space Title", visible=True), gr.update(visible=False) if not system_prompt or not system_prompt.strip(): return gr.update(value="Error: Please provide a System Prompt", visible=True), gr.update(visible=False) try: filename = generate_zip(name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, url1, url2, url3, url4) success_msg = f"""**Deployment package ready!** **File**: `{filename}` **What's included:** - `app.py` - Ready-to-deploy chat interface - `requirements.txt` - Dependencies - `README.md` - Step-by-step deployment guide - `config.json` - Configuration backup **Next steps:** 1. Download the zip file below 2. Follow the README instructions to deploy on HuggingFace Spaces 3. Set your `{api_key_var}` secret in Space settings **Your Space will be live in minutes!**""" return gr.update(value=success_msg, visible=True), gr.update(value=filename, visible=True) except Exception as e: return gr.update(value=f"Error: {str(e)}", visible=True), gr.update(visible=False) def respond(message, chat_history, url1="", url2="", url3="", url4=""): # Make actual API request to OpenRouter import os import requests # Get API key from environment api_key = os.environ.get("OPENROUTER_API_KEY") if not api_key: response = "Please set your OPENROUTER_API_KEY in the Space settings to use the chat support." chat_history.append([message, response]) return "", chat_history # Get grounding context from URLs grounding_urls = [url1, url2, url3, url4] grounding_context = get_grounding_context(grounding_urls) # Build enhanced system prompt with grounding context base_system_prompt = """You are a helpful assistant specializing in creating chat UIs for HuggingFace Spaces. You help users configure assistants for education and research. Provide concise, practical advice about: - System prompts for different use cases (courses, research, tutoring) - Model selection (recommending google/gemma-2-27b-it as a great balance) - HuggingFace Space deployment tips - Customization options Keep responses brief and actionable. Focus on what the user is specifically asking about.""" enhanced_system_prompt = base_system_prompt + grounding_context # Build conversation history for API messages = [{ "role": "system", "content": enhanced_system_prompt }] # Add conversation history - Gradio 4.x uses list/tuple format for chat in chat_history: if isinstance(chat, (list, tuple)) and len(chat) >= 2: user_msg, assistant_msg = chat[0], chat[1] if user_msg: messages.append({"role": "user", "content": user_msg}) if assistant_msg: messages.append({"role": "assistant", "content": assistant_msg}) # Add current message messages.append({"role": "user", "content": message}) try: # Make API request to OpenRouter response = requests.post( url="https://openrouter.ai/api/v1/chat/completions", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }, json={ "model": "google/gemma-2-27b-it", "messages": messages, "temperature": 0.7, "max_tokens": 500 } ) if response.status_code == 200: assistant_response = response.json()['choices'][0]['message']['content'] else: assistant_response = f"Error: {response.status_code} - {response.text}" except Exception as e: assistant_response = f"Error: {str(e)}" chat_history.append([message, assistant_response]) return "", chat_history def clear_chat(): return "", [] # Create Gradio interface with proper tab structure with gr.Blocks(title="Chat U/I Helper") as demo: with gr.Tabs(): with gr.Tab("Spaces Configuration"): gr.Markdown("# Spaces Configuration") gr.Markdown("Convert custom assistants from HuggingChat into chat interfaces with HuggingFace Spaces. Configure and download everything needed to deploy a simple HF space using Gradio.") with gr.Column(): name = gr.Textbox( label="Space Title", placeholder="My Course Helper", value="My Custom Space" ) description = gr.Textbox( label="Space Description", placeholder="A customizable AI chat interface for...", lines=2, value="An AI research assistant tailored for academic inquiry and scholarly dialogue" ) model = gr.Dropdown( label="Model", choices=MODELS, value=MODELS[0], info="Choose based on your needs and budget" ) api_key_var = gr.Textbox( label="API Key Variable Name", value="OPENROUTER_API_KEY", info="Name for the secret in HuggingFace Space settings" ) system_prompt = gr.Textbox( label="System Prompt", placeholder="You are a research assistant...", lines=4, value="You are a knowledgeable academic research assistant. Provide thoughtful, evidence-based guidance for scholarly work, literature reviews, and academic writing. Support students and researchers with clear explanations and critical thinking." ) examples_text = gr.Textbox( label="Example Prompts (one per line)", placeholder="Hello! How can you help me?\nWhat's the weather like?\nExplain quantum computing", lines=3, info="These will appear as clickable examples in the chat interface" ) gr.Markdown("### URL Grounding (Optional)") gr.Markdown("Add up to 4 URLs to provide context. Content will be fetched and added to the system prompt.") url1 = gr.Textbox( label="URL 1", placeholder="https://example.com/page1", info="First URL for context grounding" ) url2 = gr.Textbox( label="URL 2", placeholder="https://example.com/page2", info="Second URL for context grounding" ) url3 = gr.Textbox( label="URL 3", placeholder="https://example.com/page3", info="Third URL for context grounding" ) url4 = gr.Textbox( label="URL 4", placeholder="https://example.com/page4", info="Fourth URL for context grounding" ) with gr.Row(): temperature = gr.Slider( label="Temperature", minimum=0, maximum=2, value=0.7, step=0.1, info="Higher = more creative, Lower = more focused" ) max_tokens = gr.Slider( label="Max Response Tokens", minimum=50, maximum=4096, value=1024, step=50 ) generate_btn = gr.Button("Generate Deployment Package", variant="primary") status = gr.Markdown(visible=False) download_file = gr.File(label="Download your zip package", visible=False) # Connect the generate button generate_btn.click( on_generate, inputs=[name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, url1, url2, url3, url4], outputs=[status, download_file] ) with gr.Tab("Chat Support"): gr.Markdown("# Chat Support") gr.Markdown("Get personalized guidance on configuring chat assistants as HuggingFace Spaces for educational & research purposes.") # Meta chat interface with gr.Column(): chatbot = gr.Chatbot( value=[], label="Chat Support Assistant", height=400 ) msg = gr.Textbox( label="Ask about configuring chat UIs for courses, research, or custom HuggingFace Spaces", placeholder="How can I configure a chat UI for my senior seminar?", lines=2 ) with gr.Accordion("URL Grounding (Optional)", open=False): gr.Markdown("Add URLs to provide additional context for more informed responses") chat_url1 = gr.Textbox( label="URL 1", placeholder="https://example.com/context1", info="First URL for context" ) chat_url2 = gr.Textbox( label="URL 2", placeholder="https://example.com/context2", info="Second URL for context" ) chat_url3 = gr.Textbox( label="URL 3", placeholder="https://example.com/context3", info="Third URL for context" ) chat_url4 = gr.Textbox( label="URL 4", placeholder="https://example.com/context4", info="Fourth URL for context" ) with gr.Row(): submit = gr.Button("Send", variant="primary") clear = gr.Button("Clear") gr.Examples( examples=[ "How do I set up a course assistant?", "Which model should I use?", "What's a good system prompt?", "Why Gradio? What is it?", "How do I customize the chat interface?", "Can you help me troubleshoot?", ], inputs=msg ) # Connect the chat functionality submit.click(respond, [msg, chatbot, chat_url1, chat_url2, chat_url3, chat_url4], [msg, chatbot]) msg.submit(respond, [msg, chatbot, chat_url1, chat_url2, chat_url3, chat_url4], [msg, chatbot]) clear.click(clear_chat, outputs=[msg, chatbot]) if __name__ == "__main__": demo.launch(share=True)