File size: 7,084 Bytes
b5df735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7035546
 
b5df735
7035546
 
 
 
 
 
 
 
 
b5df735
7035546
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b5df735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7035546
 
 
 
 
b5df735
 
1bd8ab8
7035546
b5df735
 
 
1bd8ab8
b5df735
 
 
 
 
 
 
 
 
 
 
 
7035546
 
 
 
 
 
b5df735
 
7035546
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# FastAPI + Gradio + FastMCP MCP server main entry point

import modal
from contextlib import asynccontextmanager
from fastapi import FastAPI
from gradio.routes import mount_gradio_app
import os
from dotenv import load_dotenv
import uvicorn
from mcp.server.fastmcp import FastMCP

# Import modules
from .tools import mcp_tools  # Import the module, not get_mcp_server function
from .ui.gradio_ui import create_gradio_interface
from .config.config import is_modal_mode, is_local_mode

# Always import modal config since this module might be imported in modal context
try:
    from .config.modal_config import app, image, volume, cache_dir, secrets
    _modal_available = True
except ImportError:
    _modal_available = False

# ==================== Application Creation Function ====================

def create_app():
    """Create and return complete Gradio + MCP application"""
    
    print("πŸš€ Starting Gradio + FastMCP server")
    
    # Check if this is HF Spaces environment
    is_hf_spaces = "SPACE_ID" in os.environ
    
    if is_hf_spaces:
        # For HF Spaces: return pure Gradio app (simpler approach)
        print("πŸ€— Creating HF Spaces compatible app")
        ui_app = create_gradio_interface()
        
        print("βœ… Server startup completed")
        print("🎨 Gradio UI: /")
        
        return ui_app
    
    else:
        # For other environments: full FastAPI + MCP + Gradio setup
        # Create FastMCP server with new tools
        mcp = FastMCP("Podcast MCP")
        
        # Register tools using the new service architecture
        @mcp.tool(description="Transcribe audio files to text using Whisper model with speaker diarization support")
        async def transcribe_audio_file_tool(
            audio_file_path: str,
            model_size: str = "turbo",
            language: str = None,
            output_format: str = "srt",
            enable_speaker_diarization: bool = False
        ):
            return await mcp_tools.transcribe_audio_file(
                audio_file_path, model_size, language, output_format, enable_speaker_diarization
            )
        
        @mcp.tool(description="Download Apple Podcast audio files")
        async def download_apple_podcast_tool(url: str):
            return await mcp_tools.download_apple_podcast(url)
        
        @mcp.tool(description="Download XiaoYuZhou podcast audio files")
        async def download_xyz_podcast_tool(url: str):
            return await mcp_tools.download_xyz_podcast(url)
        
        @mcp.tool(description="Scan directory for MP3 audio files")
        async def get_mp3_files_tool(directory: str):
            return await mcp_tools.get_mp3_files(directory)
        
        @mcp.tool(description="Get basic file information")
        async def get_file_info_tool(file_path: str):
            return await mcp_tools.get_file_info(file_path)
        
        @mcp.tool(description="Read text file content in segments")
        async def read_text_file_segments_tool(
            file_path: str,
            chunk_size: int = 65536,
            start_position: int = 0
        ):
            return await mcp_tools.read_text_file_segments(file_path, chunk_size, start_position)
        
        # Create FastAPI wrapper
        fastapi_wrapper = FastAPI(
            title="Modal AudioTranscriber MCP",
            description="Gradio UI + FastMCP Tool + Modal Integration AudioTranscriber MCP",
            version="1.0.0",
            lifespan=lambda app: mcp.session_manager.run()
        )
        
        # Get FastMCP's streamable HTTP app
        mcp_app = mcp.streamable_http_app()
        
        # Mount FastMCP application to /api path  
        fastapi_wrapper.mount("/api", mcp_app)
        
        # Create Gradio interface
        ui_app = create_gradio_interface()
        
        # Use Gradio's standard mounting approach
        final_app = mount_gradio_app(
            app=fastapi_wrapper,
            blocks=ui_app,
            path="/",
            app_kwargs={
                "docs_url": "/docs",
                "redoc_url": "/redoc",
            }
        )
        
        print("βœ… Server startup completed")
        print("🎨 Gradio UI: /")
        print("πŸ”§ MCP Streamable HTTP: /api/mcp")
        print(f"πŸ“ Server name: {mcp.name}")
        
        return final_app

# ==================== Modal Deployment Configuration ====================

# Create a separate Modal app for the Gradio interface
if _modal_available:
    gradio_mcp_app = modal.App(name="gradio-mcp-ui")
    
    @gradio_mcp_app.function(
        image=image,
        cpu=2,  # Adequate CPU for UI operations
        memory=4096,  # 4GB memory for stable UI performance
        max_containers=5,  # Reduced to control resource usage
        min_containers=1,  # Keep minimum containers for faster response
        scaledown_window=600,  # 20 minutes before scaling down
        timeout=1800,  # 30 minutes timeout to prevent preemption
        volumes={cache_dir: volume},
        secrets=secrets,
    )
    @modal.concurrent(max_inputs=100)
    @modal.asgi_app()
    def app_entry():
        """Modal deployment function - create and return complete Gradio + MCP application"""
        return create_app()

# ==================== Main Entry Point ====================

def main():
    """Main entry point for all deployment modes"""
    
    if is_modal_mode():
        print("☁️ Modal mode: Use 'modal deploy src.app::gradio_mcp_app'")
        return None
    else:
        print("🏠 Starting in local mode")
        print("πŸ’‘ GPU functions will be routed to Modal endpoints")
        
        app = create_app()
        return app

def run_local():
    """Run local server with uvicorn (for direct execution)"""
    # Double-check: don't run uvicorn in HF Spaces
    if os.environ.get("HF_SPACES_MODE"):
        print("⚠️ Skipping uvicorn.run() in HF Spaces mode")
        return
        
    app = main()
    if app:
        # Use port 7860 for HF Spaces compatibility, 8000 for local
        port = int(os.environ.get("PORT", 8000))  # Use 8000 for local dev
        uvicorn.run(
            app,
            host="0.0.0.0",
            port=port,
            reload=False
        )

# ==================== Hugging Face Spaces Support ====================

# For Hugging Face Spaces, directly create the app
def get_app():
    """Get app instance for HF Spaces"""
    if "DEPLOYMENT_MODE" not in os.environ:
        os.environ["DEPLOYMENT_MODE"] = "local"
    return main()

# HF Spaces compatibility: only create app when not in main execution
if __name__ != "__main__":
    # Check if we're in HF Spaces mode and app is already created
    if not os.environ.get("HF_SPACES_MODE"):
        # This will be called when imported by other environments (not HF Spaces)
        app = get_app()

if __name__ == "__main__":
    # Check if we should run uvicorn (not in HF Spaces)
    if not os.environ.get("HF_SPACES_MODE"):
        run_local()
    else:
        print("⚠️ Skipping uvicorn in HF Spaces mode")