IvanLayer7 commited on
Commit
664bdbb
·
verified ·
1 Parent(s): 0d542e4

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +367 -325
app.py CHANGED
@@ -1,325 +1,367 @@
1
- """
2
- Hugging Face Spaces version of the Keyword Spotting App.
3
- Simplified for deployment without local authentication.
4
- """
5
-
6
- import gradio as gr
7
- import numpy as np
8
- import torch
9
- import os
10
- from typing import Dict, Any, Tuple, Optional
11
- import warnings
12
-
13
- # Import our custom modules
14
- from audio_processor import AudioProcessor
15
- from whisper_classifier import WhisperKeywordSpotter
16
-
17
- warnings.filterwarnings("ignore")
18
-
19
-
20
- class KeywordSpottingApp:
21
- """Main application class for the keyword spotting interface."""
22
-
23
- def __init__(self, model_size: str = "base"):
24
- """Initialize the application components."""
25
- print("Initializing Keyword Spotting App for Hugging Face...")
26
-
27
- # Initialize components
28
- self.audio_processor = AudioProcessor(target_sample_rate=48000, max_duration=30.0)
29
- self.classifier = WhisperKeywordSpotter(model_size=model_size)
30
-
31
- print("App initialized successfully!")
32
-
33
- def change_model(self, new_model_size: str) -> str:
34
- """Change the Whisper model size."""
35
- try:
36
- success = self.classifier.change_model(new_model_size)
37
- if success:
38
- return f"✅ Successfully changed to {new_model_size} model"
39
- else:
40
- return f"❌ Failed to change to {new_model_size} model"
41
- except Exception as e:
42
- return f"❌ Error changing model: {str(e)}"
43
-
44
- def process_audio_and_classify(
45
- self,
46
- audio_input: Optional[Tuple[int, np.ndarray]],
47
- audio_file: Optional[str],
48
- keywords: str
49
- ) -> Tuple[Dict[str, float], str]:
50
- """
51
- Process audio input and perform keyword classification.
52
-
53
- Args:
54
- audio_input: Tuple of (sample_rate, audio_array) from microphone
55
- audio_file: Path to uploaded audio file
56
- keywords: Comma-separated keywords string
57
-
58
- Returns:
59
- Tuple of (classification_results, status_message)
60
- """
61
- try:
62
- # Validate keywords
63
- if not keywords or not keywords.strip():
64
- return {}, "❌ Por favor, ingrese al menos una palabra clave."
65
-
66
- # Determine audio source and process
67
- audio_tensor = None
68
- source_info = ""
69
-
70
- if audio_file is not None:
71
- # Process uploaded file
72
- try:
73
- audio_tensor = self.audio_processor.process_audio_file(audio_file)
74
- source_info = f"📁 Archivo: {os.path.basename(audio_file)}"
75
- except Exception as e:
76
- return {}, f"❌ Error procesando archivo: {str(e)}"
77
-
78
- elif audio_input is not None:
79
- # Process microphone input
80
- try:
81
- sample_rate, audio_array = audio_input
82
- # Convert to float32 if needed
83
- if audio_array.dtype == np.int16:
84
- audio_array = audio_array.astype(np.float32) / 32768.0
85
- elif audio_array.dtype == np.int32:
86
- audio_array = audio_array.astype(np.float32) / 2147483648.0
87
-
88
- audio_tensor = self.audio_processor.process_audio_array(audio_array, sample_rate)
89
- source_info = "🎤 Micrófono"
90
- except Exception as e:
91
- return {}, f"❌ Error procesando audio del micrófono: {str(e)}"
92
- else:
93
- return {}, "❌ Por favor, grabe audio o suba un archivo de audio."
94
-
95
- # Perform classification
96
- results = self.classifier.classify_keywords(audio_tensor, keywords)
97
-
98
- if "error" in results:
99
- return {}, f" Error en clasificación: {results['error']}"
100
-
101
- # Create status message
102
- num_keywords = len([k for k in keywords.split(",") if k.strip()])
103
- status_msg = f"✅ Clasificación completada | {source_info} | {num_keywords} palabra(s) clave"
104
-
105
- return results, status_msg
106
-
107
- except Exception as e:
108
- error_msg = f"❌ Error inesperado: {str(e)}"
109
- print(error_msg)
110
- return {}, error_msg
111
-
112
- def format_results_for_display(self, results: Dict[str, float]) -> str:
113
- """
114
- Format classification results for display.
115
-
116
- Args:
117
- results: Classification results dictionary
118
-
119
- Returns:
120
- Formatted string for display
121
- """
122
- if not results:
123
- return "No hay resultados para mostrar."
124
-
125
- if "error" in results:
126
- return f"Error: {results['error']}"
127
-
128
- # Sort results by probability (descending)
129
- sorted_results = sorted(results.items(), key=lambda x: x[1], reverse=True)
130
-
131
- output_lines = ["📊 **Resultados de Clasificación:**\n"]
132
-
133
- for keyword, probability in sorted_results:
134
- # Create visual probability bar
135
- bar_length = 20
136
- filled_length = int(bar_length * probability)
137
- bar = "█" * filled_length + "░" * (bar_length - filled_length)
138
-
139
- # Color coding based on probability
140
- if probability >= 0.7:
141
- emoji = "🟢" # High confidence
142
- elif probability >= 0.4:
143
- emoji = "🟡" # Medium confidence
144
- else:
145
- emoji = "🔴" # Low confidence
146
-
147
- percentage = probability * 100
148
- output_lines.append(
149
- f"{emoji} **{keyword.upper()}**: {percentage:.1f}% [{bar}]"
150
- )
151
-
152
- return "\n".join(output_lines)
153
-
154
-
155
- def create_gradio_interface():
156
- """Create and configure the Gradio interface for Hugging Face."""
157
-
158
- # Initialize the app with default model
159
- app = KeywordSpottingApp(model_size="base")
160
-
161
- def classify_audio(audio_input, audio_file, keywords, model_size):
162
- """Wrapper function for Gradio interface."""
163
- # Change model if needed
164
- model_change_msg = app.change_model(model_size)
165
-
166
- results, status = app.process_audio_and_classify(audio_input, audio_file, keywords)
167
- formatted_results = app.format_results_for_display(results)
168
-
169
- # Add model info to status
170
- status_with_model = f"{status} | Model: {model_size}"
171
-
172
- return formatted_results, status_with_model, model_change_msg
173
-
174
- # Create the interface
175
- with gr.Blocks(
176
- title="🎯 Zero-Shot Audio Keyword Spotting",
177
- theme=gr.themes.Soft(),
178
- css="""
179
- .gradio-container {
180
- max-width: 900px !important;
181
- margin: auto !important;
182
- }
183
- .status-box {
184
- padding: 10px;
185
- border-radius: 5px;
186
- margin: 10px 0;
187
- }
188
- """
189
- ) as interface:
190
-
191
- gr.Markdown("""
192
- # 🎯 Zero-Shot Audio Keyword Spotting
193
-
194
- Detect keywords in Spanish audio using **Whisper AI** without prior training.
195
- Transcribes audio and matches keywords with high accuracy.
196
-
197
- ## 📋 Instructions:
198
- 1. **Select Whisper model** (tiny=fastest, medium=most accurate)
199
- 2. **Enter keywords** you want to detect (comma-separated)
200
- 3. **Record audio** using microphone OR **upload audio file**
201
- 4. **Click "Analyze Audio"** to get results
202
-
203
- ### 💡 Example Keywords:
204
- `hola, gracias, adiós, sí, no, por favor`
205
- """)
206
-
207
- with gr.Row():
208
- with gr.Column(scale=1):
209
- gr.Markdown("### 🤖 Model Selection")
210
- model_selector = gr.Dropdown(
211
- choices=["tiny", "base", "small", "medium"],
212
- value="tiny",
213
- label="Whisper Model",
214
- info="tiny=fastest, base=balanced, small=better accuracy, medium=best accuracy"
215
- )
216
-
217
- gr.Markdown("### 🔤 Keywords")
218
- gr.Markdown("*Example: hola, gracias, adiós*")
219
- keywords_input = gr.Textbox(
220
- label="Keywords (comma-separated)",
221
- placeholder="hola, gracias, adiós, sí, no",
222
- lines=2,
223
- value="hola, gracias, adiós, sí, no"
224
- )
225
-
226
- gr.Markdown("### 🎵 Audio Input")
227
-
228
- with gr.Tab("🎤 Record Audio"):
229
- gr.Markdown("*Click to record (max 30 seconds)*")
230
- audio_input = gr.Audio(
231
- sources=["microphone"],
232
- type="numpy",
233
- label="Record your audio here"
234
- )
235
-
236
- with gr.Tab("📁 Upload File"):
237
- gr.Markdown("*Supported: WAV, MP3, M4A, etc.*")
238
- audio_file = gr.Audio(
239
- sources=["upload"],
240
- type="filepath",
241
- label="Upload audio file"
242
- )
243
-
244
- analyze_btn = gr.Button(
245
- "🔍 Analyze Audio",
246
- variant="primary",
247
- size="lg"
248
- )
249
-
250
- with gr.Column(scale=1):
251
- gr.Markdown("### 📊 Results")
252
-
253
- results_output = gr.Markdown(
254
- value="Results will appear here after analysis...",
255
- label="Classification Results"
256
- )
257
-
258
- status_output = gr.Textbox(
259
- label="Status",
260
- value="Ready to analyze",
261
- interactive=False,
262
- elem_classes=["status-box"]
263
- )
264
-
265
- model_status_output = gr.Textbox(
266
- label="Model Status",
267
- value="Current model: base",
268
- interactive=False,
269
- elem_classes=["status-box"]
270
- )
271
-
272
- # Event handlers
273
- analyze_btn.click(
274
- fn=classify_audio,
275
- inputs=[audio_input, audio_file, keywords_input, model_selector],
276
- outputs=[results_output, status_output, model_status_output]
277
- )
278
-
279
- # Examples section
280
- gr.Markdown("""
281
- ## 💡 Usage Examples:
282
-
283
- **Suggested Spanish keywords:**
284
- - Greetings: `hola, buenos días, buenas tardes, adiós`
285
- - Courtesy: `gracias, por favor, disculpe, perdón`
286
- - Responses: `sí, no, tal vez, claro`
287
- - Numbers: `uno, dos, tres, cuatro, cinco`
288
- - Colors: `rojo, azul, verde, amarillo`
289
-
290
- **Tips:**
291
- - Use clear audio without background noise
292
- - Speak at normal speed
293
- - Keywords can appear anywhere in the audio
294
- - Works best with common Spanish words
295
-
296
- ## 🔧 Technical Details:
297
- - **Model**: OpenAI Whisper (speech transcription)
298
- - **Languages**: Optimized for Spanish, works with others
299
- - **Processing**: Up to 30 seconds, 48kHz sampling rate
300
- - **Approach**: Transcription + text matching
301
-
302
- ## 🤖 Model Comparison:
303
- - **tiny**: Fastest, basic accuracy (72MB)
304
- - **base**: Balanced speed/accuracy (139MB)
305
- - **small**: Better accuracy, slower (461MB)
306
- - **medium**: Best accuracy, slowest (1.46GB)
307
- """)
308
-
309
- return interface
310
-
311
-
312
- # Main execution for Hugging Face Spaces
313
- if __name__ == "__main__":
314
- print("🚀 Starting Keyword Spotting App on Hugging Face Spaces...")
315
-
316
- # Create and launch the interface
317
- interface = create_gradio_interface()
318
-
319
- # Launch without authentication (HF Spaces handles this)
320
- interface.launch(
321
- server_name="0.0.0.0",
322
- server_port=7860,
323
- share=False,
324
- show_error=True
325
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hugging Face Spaces version of the Keyword Spotting App.
3
+ Simplified for deployment without local authentication.
4
+ """
5
+
6
+ import gradio as gr
7
+ import numpy as np
8
+ import torch
9
+ import os
10
+ from typing import Dict, Any, Tuple, Optional
11
+ import warnings
12
+
13
+ # Import our custom modules
14
+ from audio_processor import AudioProcessor
15
+ from whisper_classifier import WhisperKeywordSpotter
16
+
17
+ warnings.filterwarnings("ignore")
18
+
19
+
20
+ def get_auth_token():
21
+ """Get authentication token from environment variables."""
22
+ # Default token if not set in environment
23
+ default_token = "layer7"
24
+
25
+ # Try to get from environment variable
26
+ token = os.getenv("ACCESS_TOKEN", default_token)
27
+
28
+ return token
29
+
30
+
31
+ def authenticate_user(token: str) -> bool:
32
+ """
33
+ Simple token-based authentication.
34
+
35
+ Args:
36
+ token: User provided token
37
+
38
+ Returns:
39
+ True if token is valid, False otherwise
40
+ """
41
+ valid_token = get_auth_token()
42
+ return token == valid_token
43
+
44
+
45
+ class KeywordSpottingApp:
46
+ """Main application class for the keyword spotting interface."""
47
+
48
+ def __init__(self, model_size: str = "base"):
49
+ """Initialize the application components."""
50
+ print("Initializing Keyword Spotting App for Hugging Face...")
51
+
52
+ # Initialize components
53
+ self.audio_processor = AudioProcessor(target_sample_rate=48000, max_duration=30.0)
54
+ self.classifier = WhisperKeywordSpotter(model_size=model_size)
55
+
56
+ print("App initialized successfully!")
57
+
58
+ def change_model(self, new_model_size: str) -> str:
59
+ """Change the Whisper model size."""
60
+ try:
61
+ success = self.classifier.change_model(new_model_size)
62
+ if success:
63
+ return f"✅ Successfully changed to {new_model_size} model"
64
+ else:
65
+ return f"❌ Failed to change to {new_model_size} model"
66
+ except Exception as e:
67
+ return f"❌ Error changing model: {str(e)}"
68
+
69
+ def process_audio_and_classify(
70
+ self,
71
+ audio_input: Optional[Tuple[int, np.ndarray]],
72
+ audio_file: Optional[str],
73
+ keywords: str
74
+ ) -> Tuple[Dict[str, float], str]:
75
+ """
76
+ Process audio input and perform keyword classification.
77
+
78
+ Args:
79
+ audio_input: Tuple of (sample_rate, audio_array) from microphone
80
+ audio_file: Path to uploaded audio file
81
+ keywords: Comma-separated keywords string
82
+
83
+ Returns:
84
+ Tuple of (classification_results, status_message)
85
+ """
86
+ try:
87
+ # Validate keywords
88
+ if not keywords or not keywords.strip():
89
+ return {}, " Por favor, ingrese al menos una palabra clave."
90
+
91
+ # Determine audio source and process
92
+ audio_tensor = None
93
+ source_info = ""
94
+
95
+ if audio_file is not None:
96
+ # Process uploaded file
97
+ try:
98
+ audio_tensor = self.audio_processor.process_audio_file(audio_file)
99
+ source_info = f"📁 Archivo: {os.path.basename(audio_file)}"
100
+ except Exception as e:
101
+ return {}, f"❌ Error procesando archivo: {str(e)}"
102
+
103
+ elif audio_input is not None:
104
+ # Process microphone input
105
+ try:
106
+ sample_rate, audio_array = audio_input
107
+ # Convert to float32 if needed
108
+ if audio_array.dtype == np.int16:
109
+ audio_array = audio_array.astype(np.float32) / 32768.0
110
+ elif audio_array.dtype == np.int32:
111
+ audio_array = audio_array.astype(np.float32) / 2147483648.0
112
+
113
+ audio_tensor = self.audio_processor.process_audio_array(audio_array, sample_rate)
114
+ source_info = "🎤 Micrófono"
115
+ except Exception as e:
116
+ return {}, f"❌ Error procesando audio del micrófono: {str(e)}"
117
+ else:
118
+ return {}, "❌ Por favor, grabe audio o suba un archivo de audio."
119
+
120
+ # Perform classification
121
+ results = self.classifier.classify_keywords(audio_tensor, keywords)
122
+
123
+ if "error" in results:
124
+ return {}, f"❌ Error en clasificación: {results['error']}"
125
+
126
+ # Create status message
127
+ num_keywords = len([k for k in keywords.split(",") if k.strip()])
128
+ status_msg = f"✅ Clasificación completada | {source_info} | {num_keywords} palabra(s) clave"
129
+
130
+ return results, status_msg
131
+
132
+ except Exception as e:
133
+ error_msg = f"❌ Error inesperado: {str(e)}"
134
+ print(error_msg)
135
+ return {}, error_msg
136
+
137
+ def format_results_for_display(self, results: Dict[str, float]) -> str:
138
+ """
139
+ Format classification results for display.
140
+
141
+ Args:
142
+ results: Classification results dictionary
143
+
144
+ Returns:
145
+ Formatted string for display
146
+ """
147
+ if not results:
148
+ return "No hay resultados para mostrar."
149
+
150
+ if "error" in results:
151
+ return f"Error: {results['error']}"
152
+
153
+ # Sort results by probability (descending)
154
+ sorted_results = sorted(results.items(), key=lambda x: x[1], reverse=True)
155
+
156
+ output_lines = ["📊 **Resultados de Clasificación:**\n"]
157
+
158
+ for keyword, probability in sorted_results:
159
+ # Create visual probability bar
160
+ bar_length = 20
161
+ filled_length = int(bar_length * probability)
162
+ bar = "" * filled_length + "" * (bar_length - filled_length)
163
+
164
+ # Color coding based on probability
165
+ if probability >= 0.7:
166
+ emoji = "🟢" # High confidence
167
+ elif probability >= 0.4:
168
+ emoji = "🟡" # Medium confidence
169
+ else:
170
+ emoji = "🔴" # Low confidence
171
+
172
+ percentage = probability * 100
173
+ output_lines.append(
174
+ f"{emoji} **{keyword.upper()}**: {percentage:.1f}% [{bar}]"
175
+ )
176
+
177
+ return "\n".join(output_lines)
178
+
179
+
180
+ def create_gradio_interface():
181
+ """Create and configure the Gradio interface for Hugging Face."""
182
+
183
+ # Initialize the app with default model
184
+ app = KeywordSpottingApp(model_size="base")
185
+
186
+ def classify_audio(audio_input, audio_file, keywords, model_size, access_token):
187
+ """Wrapper function for Gradio interface."""
188
+ # Check authentication first
189
+ if not authenticate_user(access_token):
190
+ return "❌ **Access Denied**: Invalid token. Please enter the correct access token.", "❌ Authentication failed", "❌ Access denied"
191
+
192
+ # Change model if needed
193
+ model_change_msg = app.change_model(model_size)
194
+
195
+ results, status = app.process_audio_and_classify(audio_input, audio_file, keywords)
196
+ formatted_results = app.format_results_for_display(results)
197
+
198
+ # Add model info to status
199
+ status_with_model = f"{status} | Model: {model_size}"
200
+
201
+ return formatted_results, status_with_model, model_change_msg
202
+
203
+ # Create the interface
204
+ with gr.Blocks(
205
+ title="🎯 Zero-Shot Audio Keyword Spotting",
206
+ theme=gr.themes.Soft(),
207
+ css="""
208
+ .gradio-container {
209
+ max-width: 900px !important;
210
+ margin: auto !important;
211
+ }
212
+ .status-box {
213
+ padding: 10px;
214
+ border-radius: 5px;
215
+ margin: 10px 0;
216
+ }
217
+ """
218
+ ) as interface:
219
+
220
+ gr.Markdown("""
221
+ # 🎯 Zero-Shot Audio Keyword Spotting
222
+
223
+ Detect keywords in Spanish audio using **Whisper AI** without prior training.
224
+ Transcribes audio and matches keywords with high accuracy.
225
+
226
+ ## 📋 Instructions:
227
+ 1. **Enter access token** to authenticate
228
+ 2. **Select Whisper model** (tiny=fastest, medium=most accurate)
229
+ 3. **Enter keywords** you want to detect (comma-separated)
230
+ 4. **Record audio** using microphone OR **upload audio file**
231
+ 5. **Click "Analyze Audio"** to get results
232
+
233
+ ### 💡 Example Keywords:
234
+ `hola, gracias, adiós, sí, no, por favor`
235
+ """)
236
+
237
+ with gr.Row():
238
+ with gr.Column(scale=1):
239
+ gr.Markdown("### 🔐 Authentication")
240
+ access_token_input = gr.Textbox(
241
+ label="Access Token",
242
+ placeholder="Enter access token",
243
+ type="password",
244
+ info="Required to use the application"
245
+ )
246
+
247
+ gr.Markdown("### 🤖 Model Selection")
248
+ model_selector = gr.Dropdown(
249
+ choices=["tiny", "base", "small", "medium"],
250
+ value="base",
251
+ label="Whisper Model",
252
+ info="tiny=fastest, base=balanced, small=better accuracy, medium=best accuracy"
253
+ )
254
+
255
+ gr.Markdown("### 🔤 Keywords")
256
+ gr.Markdown("*Example: hola, gracias, adiós*")
257
+ keywords_input = gr.Textbox(
258
+ label="Keywords (comma-separated)",
259
+ placeholder="hola, gracias, adiós, sí, no",
260
+ lines=2
261
+ )
262
+
263
+ gr.Markdown("### 🎵 Audio Input")
264
+
265
+ with gr.Tab("🎤 Record Audio"):
266
+ gr.Markdown("*Click to record (max 30 seconds)*")
267
+ audio_input = gr.Audio(
268
+ sources=["microphone"],
269
+ type="numpy",
270
+ label="Record your audio here"
271
+ )
272
+
273
+ with gr.Tab("📁 Upload File"):
274
+ gr.Markdown("*Supported: WAV, MP3, M4A, etc.*")
275
+ audio_file = gr.Audio(
276
+ sources=["upload"],
277
+ type="filepath",
278
+ label="Upload audio file"
279
+ )
280
+
281
+ analyze_btn = gr.Button(
282
+ "🔍 Analyze Audio",
283
+ variant="primary",
284
+ size="lg"
285
+ )
286
+
287
+ with gr.Column(scale=1):
288
+ gr.Markdown("### 📊 Results")
289
+
290
+ results_output = gr.Markdown(
291
+ value="Results will appear here after analysis...",
292
+ label="Classification Results"
293
+ )
294
+
295
+ status_output = gr.Textbox(
296
+ label="Status",
297
+ value="Ready to analyze",
298
+ interactive=False,
299
+ elem_classes=["status-box"]
300
+ )
301
+
302
+ model_status_output = gr.Textbox(
303
+ label="Model Status",
304
+ value="Current model: base",
305
+ interactive=False,
306
+ elem_classes=["status-box"]
307
+ )
308
+
309
+ # Event handlers
310
+ analyze_btn.click(
311
+ fn=classify_audio,
312
+ inputs=[audio_input, audio_file, keywords_input, model_selector, access_token_input],
313
+ outputs=[results_output, status_output, model_status_output]
314
+ )
315
+
316
+ # Examples section
317
+ gr.Markdown("""
318
+ ## 💡 Usage Examples:
319
+
320
+ **Suggested Spanish keywords:**
321
+ - Greetings: `hola, buenos días, buenas tardes, adiós`
322
+ - Courtesy: `gracias, por favor, disculpe, perdón`
323
+ - Responses: `sí, no, tal vez, claro`
324
+ - Numbers: `uno, dos, tres, cuatro, cinco`
325
+ - Colors: `rojo, azul, verde, amarillo`
326
+
327
+ **Tips:**
328
+ - Use clear audio without background noise
329
+ - Speak at normal speed
330
+ - Keywords can appear anywhere in the audio
331
+ - Works best with common Spanish words
332
+
333
+ ## 🔧 Technical Details:
334
+ - **Model**: OpenAI Whisper (speech transcription)
335
+ - **Languages**: Optimized for Spanish, works with others
336
+ - **Processing**: Up to 30 seconds, 48kHz sampling rate
337
+ - **Approach**: Transcription + text matching
338
+
339
+ ## 🤖 Model Comparison:
340
+ - **tiny**: Fastest, basic accuracy (72MB)
341
+ - **base**: Balanced speed/accuracy (139MB)
342
+ - **small**: Better accuracy, slower (461MB)
343
+ - **medium**: Best accuracy, slowest (1.46GB)
344
+ """)
345
+
346
+ return interface
347
+
348
+
349
+ # Main execution for Hugging Face Spaces
350
+ if __name__ == "__main__":
351
+ print("🚀 Starting Keyword Spotting App on Hugging Face Spaces...")
352
+
353
+ # Show authentication info
354
+ current_token = get_auth_token()
355
+ print(f"🔐 Access token required: {current_token}")
356
+ print("💡 Set ACCESS_TOKEN environment variable to change the token")
357
+
358
+ # Create and launch the interface
359
+ interface = create_gradio_interface()
360
+
361
+ # Launch with token-based authentication
362
+ interface.launch(
363
+ server_name="0.0.0.0",
364
+ server_port=7860,
365
+ share=False,
366
+ show_error=True
367
+ )