Emperor555 Claude commited on
Commit
0728164
·
1 Parent(s): 0df8486

Switch from Modal to Hugging Face Spaces deployment

Browse files

- Remove modal_app.py and GitHub Actions workflow
- Update README with HF Spaces frontmatter
- Clean up requirements.txt

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (4) hide show
  1. .github/workflows/deploy.yml +0 -32
  2. README.md +14 -87
  3. modal_app.py +0 -171
  4. requirements.txt +0 -2
.github/workflows/deploy.yml DELETED
@@ -1,32 +0,0 @@
1
- name: Deploy to Modal
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- deploy:
10
- name: Deploy
11
- runs-on: ubuntu-latest
12
- env:
13
- MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
14
- MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
15
-
16
- steps:
17
- - name: Checkout Repository
18
- uses: actions/checkout@v4
19
-
20
- - name: Install Python
21
- uses: actions/setup-python@v5
22
- with:
23
- python-version: "3.11"
24
-
25
- - name: Install Modal
26
- run: |
27
- python -m pip install --upgrade pip
28
- pip install modal
29
-
30
- - name: Deploy to Modal
31
- run: |
32
- modal deploy modal_app.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,3 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # 🎭 Explainor
2
 
3
  > **Learn anything through the voice of your favorite characters!**
@@ -6,8 +20,6 @@
6
  [![Track](https://img.shields.io/badge/Track-MCP%20in%20Action-blue)](https://huggingface.co/MCP-1st-Birthday)
7
  [![Category](https://img.shields.io/badge/Category-Creative-green)](https://huggingface.co/MCP-1st-Birthday)
8
 
9
- **Tags:** `mcp-in-action-track-creative`
10
-
11
  ---
12
 
13
  ## 🌟 What is Explainor?
@@ -23,10 +35,6 @@ Explainor is an AI agent that takes any topic you want to learn about and explai
23
  | 🏄 **Surfer Dude** | "Brooo", chill vibes, wave metaphors |
24
  | 🧙 **Yoda** | Inverted syntax, wise, Force references |
25
 
26
- ## 🎬 Demo
27
-
28
- [Demo Video Placeholder]
29
-
30
  ## 🛠️ How It Works
31
 
32
  1. **Enter a topic** - Anything from "Quantum Computing" to "How do volcanoes work?"
@@ -48,94 +56,13 @@ Make the explanation even more tailored by choosing your audience:
48
  | 👵 My confused grandmother | Extra simple, patient |
49
  | 🤖 A skeptical robot | Logical, evidence-based |
50
  | 👽 An alien visiting Earth | Explain Earth concepts |
51
- | 🧟 A zombie | Very short, simple words |
52
- | 🦊 A very smart fox | Clever, quick |
53
- | 👔 A stressed CEO | Bottom-line focused |
54
- | 🎮 A distracted gamer | Keep it engaging |
55
-
56
- ## 🔌 MCP Server Integration
57
-
58
- This app is a **real MCP server**! When running, it exposes its functionality as tools that other AI agents can call via the Model Context Protocol.
59
-
60
- **MCP Endpoint:** `http://localhost:7860/gradio_api/mcp/`
61
-
62
- This means:
63
- - Other AI agents can use Explainor as a tool
64
- - Enables agent-to-agent communication
65
- - Part of the growing MCP ecosystem
66
 
67
  ## 🚀 Tech Stack
68
 
69
- - **MCP**: Model Context Protocol - App exposes itself as an MCP server
70
  - **LLM**: [Nebius AI](https://nebius.com) - Llama 3.3 70B for intelligent explanations
71
  - **TTS**: [ElevenLabs](https://elevenlabs.io) - Realistic voice synthesis with character-matched voices
72
  - **Web Search**: DuckDuckGo API for topic research
73
  - **Frontend**: [Gradio](https://gradio.app) with MCP integration
74
- - **Deployment**: [Modal](https://modal.com) - Serverless infrastructure
75
-
76
- ## 💻 Local Development
77
-
78
- ### Prerequisites
79
-
80
- - Python 3.11+
81
- - Nebius API key
82
- - ElevenLabs API key
83
-
84
- ### Setup
85
-
86
- ```bash
87
- # Clone the repository
88
- git clone https://huggingface.co/spaces/MCP-1st-Birthday/explainor
89
-
90
- # Install dependencies
91
- pip install -r requirements.txt
92
-
93
- # Set up environment variables
94
- cp .env.example .env
95
- # Edit .env with your API keys
96
-
97
- # Run locally
98
- python app.py
99
- ```
100
-
101
- ### Environment Variables
102
-
103
- ```bash
104
- NEBIUS_API_KEY=your_nebius_api_key_here
105
- ELEVENLABS_API_KEY=your_elevenlabs_api_key_here
106
- ```
107
-
108
- ## 🌐 Deployment
109
-
110
- ### Modal Deployment
111
-
112
- ```bash
113
- # Set up Modal secrets
114
- modal secret create nebius-api-key NEBIUS_API_KEY=your_key
115
- modal secret create elevenlabs-api-key ELEVENLABS_API_KEY=your_key
116
-
117
- # Deploy
118
- modal deploy modal_app.py
119
- ```
120
-
121
- ### Hugging Face Spaces
122
-
123
- This app is designed to run on Hugging Face Spaces with the Gradio SDK.
124
-
125
- ## 📁 Project Structure
126
-
127
- ```
128
- explainor/
129
- ├── app.py # Main Gradio application
130
- ├── modal_app.py # Modal deployment config
131
- ├── requirements.txt # Python dependencies
132
- ├── src/
133
- │ ├── __init__.py
134
- │ ├── personas.py # Persona definitions & voice mappings
135
- │ ├── agent.py # Agent logic & web search
136
- │ └── tts.py # ElevenLabs integration
137
- └── README.md
138
- ```
139
 
140
  ## 🏆 Hackathon Submission
141
 
 
1
+ ---
2
+ title: Explainor
3
+ emoji: 🎭
4
+ colorFrom: purple
5
+ colorTo: orange
6
+ sdk: gradio
7
+ sdk_version: 5.0.0
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ tags:
12
+ - mcp-in-action-track-creative
13
+ ---
14
+
15
  # 🎭 Explainor
16
 
17
  > **Learn anything through the voice of your favorite characters!**
 
20
  [![Track](https://img.shields.io/badge/Track-MCP%20in%20Action-blue)](https://huggingface.co/MCP-1st-Birthday)
21
  [![Category](https://img.shields.io/badge/Category-Creative-green)](https://huggingface.co/MCP-1st-Birthday)
22
 
 
 
23
  ---
24
 
25
  ## 🌟 What is Explainor?
 
35
  | 🏄 **Surfer Dude** | "Brooo", chill vibes, wave metaphors |
36
  | 🧙 **Yoda** | Inverted syntax, wise, Force references |
37
 
 
 
 
 
38
  ## 🛠️ How It Works
39
 
40
  1. **Enter a topic** - Anything from "Quantum Computing" to "How do volcanoes work?"
 
56
  | 👵 My confused grandmother | Extra simple, patient |
57
  | 🤖 A skeptical robot | Logical, evidence-based |
58
  | 👽 An alien visiting Earth | Explain Earth concepts |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  ## 🚀 Tech Stack
61
 
 
62
  - **LLM**: [Nebius AI](https://nebius.com) - Llama 3.3 70B for intelligent explanations
63
  - **TTS**: [ElevenLabs](https://elevenlabs.io) - Realistic voice synthesis with character-matched voices
64
  - **Web Search**: DuckDuckGo API for topic research
65
  - **Frontend**: [Gradio](https://gradio.app) with MCP integration
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
  ## 🏆 Hackathon Submission
68
 
modal_app.py DELETED
@@ -1,171 +0,0 @@
1
- """Modal deployment configuration for Explainor.
2
-
3
- Deploy with: modal deploy modal_app.py
4
- Run locally: modal serve modal_app.py
5
- """
6
-
7
- import modal
8
-
9
- # Define the Modal app
10
- app = modal.App("explainor-v6")
11
-
12
- # Create image with dependencies
13
- image = (
14
- modal.Image.debian_slim(python_version="3.11")
15
- .pip_install(
16
- "gradio==4.44.1", # Use older stable version without aggressive SSE
17
- "elevenlabs>=1.0.0",
18
- "httpx>=0.25.0",
19
- "python-dotenv>=1.0.0",
20
- "fastapi",
21
- "uvicorn",
22
- )
23
- .add_local_dir("src", remote_path="/app/src", copy=True)
24
- )
25
-
26
-
27
- @app.function(
28
- image=image,
29
- secrets=[
30
- modal.Secret.from_name("nebius-api-key"),
31
- modal.Secret.from_name("elevenlabs-api-key"),
32
- ],
33
- timeout=600,
34
- scaledown_window=300,
35
- )
36
- @modal.web_server(port=7860, startup_timeout=120)
37
- def serve():
38
- """Serve the Gradio app via web_server."""
39
- import subprocess
40
- import sys
41
- import os
42
-
43
- os.chdir("/app")
44
- sys.path.insert(0, "/app")
45
-
46
- # Write a standalone gradio script
47
- script = '''
48
- import sys
49
- sys.path.insert(0, "/app")
50
-
51
- import os
52
- import tempfile
53
- import gradio as gr
54
- from src.personas import get_persona_names, get_persona
55
- from src.agent import run_agent
56
- from src.tts import generate_speech
57
-
58
- def format_sources(sources):
59
- if not sources:
60
- return "*No external sources used*"
61
- md = ""
62
- for i, src in enumerate(sources, 1):
63
- if src.get("url"):
64
- md += f"{i}. [{src['title']}]({src['url']})\\n"
65
- else:
66
- md += f"{i}. {src['title']} ({src.get('source', 'General')})\\n"
67
- return md
68
-
69
- def format_mcp_tools(tools):
70
- if not tools:
71
- return "*No tools used*"
72
- md = "**Agent Tool Calls:**\\n\\n"
73
- for tool in tools:
74
- md += f"| {tool['icon']} | `{tool['name']}` | {tool['desc']} |\\n"
75
- return md
76
-
77
- def explain_topic(topic, persona_name, audience=""):
78
- import traceback
79
- if not topic.strip():
80
- return "Please enter a topic!", "", "", ""
81
- if not persona_name:
82
- persona_name = "5-Year-Old"
83
-
84
- # Check API key
85
- nebius_key = os.getenv("NEBIUS_API_KEY")
86
- if not nebius_key:
87
- available_keys = [k for k in os.environ.keys() if "KEY" in k or "API" in k or "NEBIUS" in k]
88
- return f"Error: NEBIUS_API_KEY not found. Available: {available_keys}", "", "", ""
89
-
90
- steps_log = []
91
- explanation = ""
92
- sources = []
93
- mcp_tools = []
94
- try:
95
- for update in run_agent(topic, persona_name, audience):
96
- if update["type"] == "step":
97
- steps_log.append(f"**{update['title']}**\\n{update['content']}")
98
- if update["step"] == "research_done" and "sources" in update:
99
- sources = update["sources"]
100
- elif update["type"] == "result":
101
- explanation = update["explanation"]
102
- sources = update.get("sources", sources)
103
- mcp_tools = update.get("mcp_tools", [])
104
- except Exception as e:
105
- return f"Error: {str(e)}\\n\\n{traceback.format_exc()}", "", "\\n\\n---\\n\\n".join(steps_log), ""
106
- return explanation, format_sources(sources), "\\n\\n---\\n\\n".join(steps_log), format_mcp_tools(mcp_tools)
107
-
108
- def generate_audio(explanation, persona_name):
109
- if not explanation or not explanation.strip():
110
- return None
111
- if not persona_name:
112
- persona_name = "5-Year-Old"
113
- try:
114
- persona = get_persona(persona_name)
115
- audio_bytes = generate_speech(explanation, persona["voice_id"], persona.get("voice_settings"))
116
- with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
117
- f.write(audio_bytes)
118
- return f.name
119
- except Exception as e:
120
- raise gr.Error(f"Audio generation failed: {str(e)}")
121
-
122
- # Get persona names as a static list
123
- persona_names = list(get_persona_names())
124
-
125
- with gr.Blocks(title="Explainor") as demo:
126
- gr.Markdown("# Explainor\\n### Learn anything through the voice of your favorite characters!")
127
- with gr.Row():
128
- topic_input = gr.Textbox(label="Topic", placeholder="e.g., Quantum Computing")
129
- persona_dropdown = gr.Dropdown(choices=persona_names, value="5-Year-Old", label="Persona")
130
- audience_dropdown = gr.Dropdown(
131
- choices=["Just me", "Confused grandmother", "Skeptical robot", "Alien"],
132
- value="Just me",
133
- label="Audience"
134
- )
135
- explain_btn = gr.Button("Explain!", variant="primary")
136
- explanation_output = gr.Textbox(label="Explanation", lines=6)
137
- read_aloud_btn = gr.Button("Read Aloud")
138
- audio_output = gr.Audio(label="Listen", type="filepath", autoplay=True)
139
- with gr.Accordion("Tools", open=False):
140
- mcp_output = gr.Markdown("")
141
- with gr.Accordion("Sources", open=False):
142
- sources_output = gr.Markdown("")
143
- with gr.Accordion("Trace", open=False):
144
- steps_output = gr.Markdown("")
145
-
146
- def do_explain(topic, persona, audience):
147
- aud = "" if "Just me" in audience else audience
148
- return explain_topic(topic, persona, aud)
149
-
150
- explain_btn.click(
151
- fn=do_explain,
152
- inputs=[topic_input, persona_dropdown, audience_dropdown],
153
- outputs=[explanation_output, sources_output, steps_output, mcp_output],
154
- )
155
- read_aloud_btn.click(
156
- fn=generate_audio,
157
- inputs=[explanation_output, persona_dropdown],
158
- outputs=[audio_output],
159
- )
160
-
161
- if __name__ == "__main__":
162
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
163
- '''
164
-
165
- # Write to file and run
166
- with open("/app/run_gradio.py", "w") as f:
167
- f.write(script)
168
-
169
- # Run the script with environment variables
170
- env = os.environ.copy()
171
- subprocess.Popen([sys.executable, "/app/run_gradio.py"], env=env)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,6 +1,4 @@
1
  gradio[mcp]>=5.0.0
2
  elevenlabs>=1.0.0
3
- openai>=1.0.0
4
  httpx>=0.25.0
5
  python-dotenv>=1.0.0
6
- modal>=0.64.0
 
1
  gradio[mcp]>=5.0.0
2
  elevenlabs>=1.0.0
 
3
  httpx>=0.25.0
4
  python-dotenv>=1.0.0