cngsm commited on
Commit
e805d1d
·
verified ·
1 Parent(s): e317ea5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -350
app.py CHANGED
@@ -4,380 +4,172 @@ import pandas as pd
4
  from datetime import datetime
5
  import folium
6
  from folium import plugins
 
 
 
 
7
 
8
- # Configuração do banco de dados
9
- def init_db():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  conn = sqlite3.connect('locations.db', check_same_thread=False)
11
  cursor = conn.cursor()
12
  cursor.execute('''
13
- CREATE TABLE IF NOT EXISTS locations (
14
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15
- device_id TEXT NOT NULL,
16
- device_name TEXT NOT NULL,
17
- latitude REAL NOT NULL,
18
- longitude REAL NOT NULL,
19
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
20
- accuracy REAL,
21
- battery_level INTEGER,
22
- ip_address TEXT
23
- )
24
- ''')
25
- conn.commit()
26
  conn.close()
 
 
 
 
 
 
 
27
 
28
- # Função para adicionar localização
29
  def add_location(device_id, device_name, latitude, longitude, accuracy=0, battery=100):
30
- if not all([device_id, device_name, latitude, longitude]):
31
- return "❌ Erro: Todos os campos são obrigatórios!"
32
-
33
  try:
34
  lat = float(latitude)
35
  lng = float(longitude)
36
- acc = float(accuracy) if accuracy else 0
37
- bat = int(battery) if battery else 100
38
 
39
- if not (-90 <= lat <= 90) or not (-180 <= lng <= 180):
40
- return " Erro: Coordenadas inválidas!"
41
-
42
- conn = sqlite3.connect('locations.db', check_same_thread=False)
43
- cursor = conn.cursor()
44
-
45
- cursor.execute('''
46
- INSERT INTO locations (device_id, device_name, latitude, longitude, accuracy, battery_level)
47
- VALUES (?, ?, ?, ?, ?, ?)
48
- ''', (device_id, device_name, lat, lng, acc, bat))
49
-
50
- conn.commit()
51
- conn.close()
52
-
53
- return f"✅ Localização adicionada com sucesso!\n📍 {lat:.6f}, {lng:.6f}\n⏰ {datetime.now().strftime('%H:%M:%S')}"
54
-
55
- except ValueError:
56
- return "❌ Erro: Valores numéricos inválidos!"
57
- except Exception as e:
58
- return f"❌ Erro: {str(e)}"
59
-
60
- # Função para obter mapa atualizado
61
- def get_updated_map():
62
- try:
63
- conn = sqlite3.connect('locations.db', check_same_thread=False)
64
-
65
- query = '''
66
- SELECT device_id, device_name, latitude, longitude,
67
- timestamp, accuracy, battery_level,
68
- ROW_NUMBER() OVER (PARTITION BY device_id ORDER BY timestamp DESC) as rn
69
- FROM locations
70
- WHERE timestamp > datetime('now', '-1 day')
71
- '''
72
-
73
- df = pd.read_sql_query(query, conn)
74
- df_latest = df[df['rn'] == 1].copy()
75
-
76
- conn.close()
77
-
78
- if df_latest.empty:
79
- m = folium.Map(location=[-14.235, -51.9253], zoom_start=4)
80
- folium.Marker(
81
- [-14.235, -51.9253],
82
- popup="Nenhum dispositivo encontrado",
83
- icon=folium.Icon(color='gray', icon='info-sign')
84
- ).add_to(m)
85
- return m._repr_html_()
86
-
87
- center_lat = df_latest['latitude'].mean()
88
- center_lng = df_latest['longitude'].mean()
89
- m = folium.Map(location=[center_lat, center_lng], zoom_start=12)
90
-
91
- colors = ['red', 'blue', 'green', 'purple', 'orange', 'darkred', 'lightred',
92
- 'beige', 'darkblue', 'darkgreen', 'cadetblue', 'darkpurple', 'white', 'pink', 'lightblue', 'lightgreen', 'gray', 'black', 'lightgray']
93
-
94
- for idx, row in df_latest.iterrows():
95
- last_update = datetime.strptime(row['timestamp'], '%Y-%m-%d %H:%M:%S')
96
- is_online = (datetime.now() - last_update).total_seconds() < 300
97
-
98
- color = colors[idx % len(colors)]
99
- icon_color = 'green' if is_online else 'red'
100
-
101
- popup_html = f"""
102
- <div style="font-family: Arial; width: 200px;">
103
- <h4>📱 {row['device_name']}</h4>
104
- <p><b>ID:</b> {row['device_id']}</p>
105
- <p><b>Status:</b> <span style="color: {'green' if is_online else 'red'};">
106
- {'🟢 Online' if is_online else '🔴 Offline'}</span></p>
107
- <p><b>Última atualização:</b><br>{row['timestamp']}</p>
108
- <p><b>Coordenadas:</b><br>{row['latitude']:.6f}, {row['longitude']:.6f}</p>
109
- <p><b>Precisão:</b> ±{row['accuracy']:.0f}m</p>
110
- <p><b>Bateria:</b> 🔋 {row['battery_level']}%</p>
111
- </div>
112
- """
113
-
114
- folium.Marker(
115
- [row['latitude'], row['longitude']],
116
- popup=folium.Popup(popup_html, min_width=200, max_width=300),
117
- tooltip=f"{row['device_name']} - {'Online' if is_online else 'Offline'}",
118
- icon=folium.Icon(color=icon_color, icon='phone', prefix='fa')
119
- ).add_to(m)
120
 
121
- folium.Circle(
122
- [row['latitude'], row['longitude']],
123
- radius=row['accuracy'],
124
- popup=f"Área de precisão: ±{row['accuracy']:.0f}m",
125
- color=color,
126
- fill=True,
127
- fillOpacity=0.1,
128
- weight=1
129
- ).add_to(m)
130
-
131
- for device_id in df_latest['device_id'].unique():
132
- device_history = df[df['device_id'] == device_id].sort_values('timestamp')
133
- if len(device_history) > 1:
134
- coordinates = [[row['latitude'], row['longitude']] for _, row in device_history.iterrows()]
135
- folium.PolyLine(
136
- coordinates,
137
- color=colors[list(df_latest['device_id'].unique()).index(device_id) % len(colors)],
138
- weight=2,
139
- opacity=0.8,
140
- popup=f"Trajeto: {device_history.iloc[0]['device_name']}"
141
- ).add_to(m)
142
-
143
- plugins.Fullscreen().add_to(m)
144
- minimap = plugins.MiniMap()
145
- m.add_child(minimap)
146
-
147
- return m._repr_html_()
148
-
149
- except Exception as e:
150
- return f"<div style='color: red; padding: 20px;'>Erro ao carregar mapa: {str(e)}</div>"
151
-
152
- # Função para obter estatísticas
153
- def get_statistics():
154
- try:
155
- conn = sqlite3.connect('locations.db', check_same_thread=False)
156
- cursor = conn.cursor()
157
-
158
- cursor.execute("SELECT COUNT(DISTINCT device_id) FROM locations")
159
- total_devices = cursor.fetchone()[0]
160
-
161
- cursor.execute("SELECT COUNT(*) FROM locations WHERE timestamp > datetime('now', '-1 day')")
162
- locations_24h = cursor.fetchone()[0]
163
-
164
- cursor.execute("""
165
- SELECT COUNT(DISTINCT device_id) FROM locations
166
- WHERE timestamp > datetime('now', '-5 minutes')
167
- """)
168
- online_devices = cursor.fetchone()[0]
169
-
170
- cursor.execute("SELECT MAX(timestamp) FROM locations")
171
- last_update = cursor.fetchone()[0]
172
-
173
- conn.close()
174
-
175
- stats = f"""
176
- 📊 **ESTATÍSTICAS DO SISTEMA**
177
-
178
- 🔢 **Total de Dispositivos:** {total_devices}
179
- 🟢 **Dispositivos Online:** {online_devices}
180
- 🔴 **Dispositivos Offline:** {total_devices - online_devices}
181
-
182
- 📍 **Localizações (24h):** {locations_24h}
183
- ⏰ **Última Atualização:** {last_update or 'Nunca'}
184
-
185
- ---
186
- *Atualizado automaticamente*
187
- """
188
-
189
- return stats
190
-
191
- except Exception as e:
192
- return f"❌ Erro ao carregar estatísticas: {str(e)}"
193
-
194
- # Função para obter lista de dispositivos (modificada para sempre retornar DataFrame)
195
- def get_device_list():
196
- try:
197
- conn = sqlite3.connect('locations.db', check_same_thread=False)
198
-
199
- query = '''
200
- SELECT
201
- device_id,
202
- device_name,
203
- latitude,
204
- longitude,
205
- timestamp,
206
- battery_level,
207
- accuracy,
208
- ROW_NUMBER() OVER (PARTITION BY device_id ORDER BY timestamp DESC) as rn
209
- FROM locations
210
- WHERE timestamp > datetime('now', '-7 days')
211
- '''
212
-
213
- df = pd.read_sql_query(query, conn)
214
- conn.close()
215
-
216
- if df.empty:
217
- return pd.DataFrame([{
218
- "Nome": "Nenhum dispositivo encontrado",
219
- "ID": "",
220
- "Status": "",
221
- "Coordenadas": "",
222
- "Última Atualização": "",
223
- "Bateria (%)": "",
224
- "Precisão (m)": ""
225
- }])
226
-
227
- df_latest = df[df['rn'] == 1].copy()
228
-
229
- df_latest['status'] = df_latest['timestamp'].apply(
230
- lambda x: '🟢 Online' if (datetime.now() - datetime.strptime(x, '%Y-%m-%d %H:%M:%S')).total_seconds() < 300 else '🔴 Offline'
231
- )
232
-
233
- df_latest['coordenadas'] = df_latest.apply(
234
- lambda row: f"{row['latitude']:.6f}, {row['longitude']:.6f}", axis=1
235
- )
236
-
237
- display_df = df_latest[['device_name', 'device_id', 'status', 'coordenadas', 'timestamp', 'battery_level', 'accuracy']].copy()
238
- display_df.columns = ['Nome', 'ID', 'Status', 'Coordenadas', 'Última Atualização', 'Bateria (%)', 'Precisão (m)']
239
-
240
- return display_df
241
-
242
- except Exception as e:
243
- return pd.DataFrame([{
244
- "Erro": f"Erro ao carregar lista de dispositivos: {str(e)}"
245
- }])
246
-
247
- # Função para limpeza de dados antigos
248
- def cleanup_old_data():
249
- try:
250
- conn = sqlite3.connect('locations.db', check_same_thread=False)
251
- cursor = conn.cursor()
252
-
253
- cursor.execute("DELETE FROM locations WHERE timestamp < datetime('now', '-30 days')")
254
- deleted = cursor.rowcount
255
-
256
- conn.commit()
257
- conn.close()
258
-
259
- return f"✅ Limpeza concluída! {deleted} registros antigos removidos."
260
-
261
- except Exception as e:
262
- return f"❌ Erro na limpeza: {str(e)}"
263
 
264
- # Função para exportar dados
265
- def export_data():
266
- try:
267
- conn = sqlite3.connect('locations.db', check_same_thread=False)
268
- df = pd.read_sql_query("SELECT * FROM locations ORDER BY timestamp DESC", conn)
269
- conn.close()
270
-
271
- if df.empty:
272
- return None, "Nenhum dado para exportar."
273
-
274
- filename = f"localizacoes_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
275
- df.to_csv(filename, index=False, encoding='utf-8')
276
-
277
- return filename, f"✅ Dados exportados com sucesso! {len(df)} registros salvos em {filename}"
278
-
279
- except Exception as e:
280
- return None, f"❌ Erro na exportação: {str(e)}"
281
-
282
- # Interface principal usando Gradio
283
  def create_interface():
284
- init_db()
285
-
286
- with gr.Blocks(title="📍 Monitor de Localização Familiar", theme=gr.themes.Soft()) as demo:
287
  gr.Markdown("""
288
- # 📍 Sistema de Monitoramento de Localização Familiar
289
-
290
- **Monitore a localização de seus familiares com segurança e consentimento.**
291
 
292
- > ⚠️ **Importante:** Use apenas com consentimento explícito. Respeite a privacidade!
293
  """)
294
 
295
  with gr.Tabs():
296
- with gr.TabItem("📱 Enviar Localização"):
297
- gr.Markdown("### 📡 Enviar Nova Localização")
298
-
299
- with gr.Row():
300
- device_id = gr.Textbox(label="🆔 ID do Dispositivo", placeholder="Ex: celular_joao")
301
- device_name = gr.Textbox(label="📱 Nome do Dispositivo", placeholder="Ex: Celular do João")
302
-
303
- with gr.Row():
304
- latitude = gr.Textbox(label="🌐 Latitude", placeholder="Ex: -23.550520")
305
- longitude = gr.Textbox(label="🌐 Longitude", placeholder="Ex: -46.633309")
306
-
307
- with gr.Row():
308
- accuracy = gr.Number(label="🎯 Precisão (metros)", value=10)
309
- battery = gr.Slider(label="🔋 Nível da Bateria (%)", minimum=0, maximum=100, value=100, step=1)
310
-
311
- send_btn = gr.Button("📡 Enviar Localização", variant="primary", size="lg")
312
- send_result = gr.Textbox(label="Resultado", interactive=False)
313
-
314
- send_btn.click(add_location, inputs=[device_id, device_name, latitude, longitude, accuracy, battery], outputs=send_result)
315
-
316
  gr.Markdown("""
317
- **💡 Dica:** Para obter suas coordenadas atuais:
318
- 1. Abra o Google Maps no seu celular
319
- 2. Pressione e segure no seu local atual
320
- 3. Copie as coordenadas que aparecem
321
  """)
 
 
 
322
 
323
- with gr.TabItem("🗺��� Mapa em Tempo Real"):
324
- gr.Markdown("### 🌍 Visualização em Tempo Real")
325
- refresh_btn = gr.Button("🔄 Atualizar Mapa", variant="secondary")
326
- map_html = gr.HTML(label="Mapa")
327
- refresh_btn.click(get_updated_map, outputs=map_html)
328
- gr.Timer(30.0, get_updated_map, [], [map_html])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
- with gr.TabItem("📋 Lista de Dispositivos"):
331
- gr.Markdown("### 📱 Dispositivos Monitorados")
332
- refresh_devices_btn = gr.Button("🔄 Atualizar Lista", variant="secondary")
333
- devices_table = gr.Dataframe(label="Dispositivos Ativos", interactive=False)
334
- refresh_devices_btn.click(get_device_list, outputs=devices_table)
335
- gr.Timer(30.0, get_device_list, [], [devices_table])
336
 
337
- with gr.TabItem("📊 Estatísticas"):
338
- gr.Markdown("### 📈 Estatísticas do Sistema")
339
- refresh_stats_btn = gr.Button("🔄 Atualizar Estatísticas", variant="secondary")
340
- stats_display = gr.Markdown()
341
- refresh_stats_btn.click(get_statistics, outputs=stats_display)
342
- gr.Timer(60.0, get_statistics, [], [stats_display])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
- with gr.TabItem("⚙️ Administração"):
345
- gr.Markdown("### 🛠️ Ferramentas de Administração")
346
- with gr.Row():
347
- cleanup_btn = gr.Button("🧹 Limpar Dados Antigos", variant="secondary")
348
- export_btn = gr.Button("📥 Exportar Dados", variant="secondary")
349
- admin_result = gr.Textbox(label="Resultado da Operação", interactive=False)
350
- export_file = gr.File(label="Arquivo Exportado", visible=False)
351
- cleanup_btn.click(cleanup_old_data, outputs=admin_result)
352
- export_btn.click(export_data, outputs=[admin_result, export_file])
353
-
354
- gr.Markdown("""
355
- **⚠️ Informações Importantes:**
356
- - Dados são mantidos por 30 dias automaticamente
357
- - Use a limpeza manual se necessário
358
- - Faça backups regulares dos dados importantes
359
- - Este sistema respeita a privacidade - dados ficam no seu servidor
360
- """)
361
-
362
- gr.Markdown("""
363
- ---
364
- ### 📱 Como Usar no Celular:
365
- 1. **Para Enviar Localização:** Use a aba "Enviar Localização"
366
- 2. **Para Monitorar:** Use a aba "Mapa em Tempo Real"
367
- 3. **Atualizações:** O sistema atualiza automaticamente
368
-
369
- ### 🔒 Privacidade e Segurança:
370
- - ✅ Dados armazenados localmente no seu servidor
371
- - ✅ Sem compartilhamento com terceiros
372
- - ✅ Use apenas com consentimento
373
- - ✅ Transparência total sobre quando está ativo
374
-
375
- **Desenvolvido com ❤️ para famílias que querem se manter conectadas com segurança.**
376
- """)
377
-
378
- return demo
379
 
380
- # Executar aplicação
381
  if __name__ == "__main__":
382
- demo = create_interface()
383
- demo.launch()
 
 
 
 
 
4
  from datetime import datetime
5
  import folium
6
  from folium import plugins
7
+ from fastapi import FastAPI, Request
8
+ import uvicorn
9
+ from threading import Thread
10
+ from geopy.distance import geodesic
11
 
12
+ # Configuração do banco de dados e API
13
+ app = FastAPI()
14
+ init_db()
15
+
16
+ # Endpoint para receber localizações via API
17
+ @app.post("/api/location")
18
+ async def receive_location(request: Request):
19
+ data = await request.json()
20
+ device_id = data.get('device_id')
21
+ device_name = data.get('device_name')
22
+ latitude = data.get('latitude')
23
+ longitude = data.get('longitude')
24
+ accuracy = data.get('accuracy', 10)
25
+ battery = data.get('battery', 100)
26
+
27
+ result = add_location(device_id, device_name, latitude, longitude, accuracy, battery)
28
+ return {"status": "success" if "✅" in result else "error", "message": result}
29
+
30
+ # Função para verificar movimento significativo (mais de 50 metros)
31
+ def is_significant_movement(device_id, new_lat, new_lng):
32
  conn = sqlite3.connect('locations.db', check_same_thread=False)
33
  cursor = conn.cursor()
34
  cursor.execute('''
35
+ SELECT latitude, longitude FROM locations
36
+ WHERE device_id = ?
37
+ ORDER BY timestamp DESC LIMIT 1
38
+ ''', (device_id,))
39
+ last_location = cursor.fetchone()
 
 
 
 
 
 
 
 
40
  conn.close()
41
+
42
+ if not last_location:
43
+ return True
44
+
45
+ old_lat, old_lng = last_location
46
+ distance = geodesic((old_lat, old_lng), (new_lat, new_lng)).meters
47
+ return distance > 50 # Só atualiza se mover mais de 50 metros
48
 
49
+ # Modificação na função add_location para verificar movimento
50
  def add_location(device_id, device_name, latitude, longitude, accuracy=0, battery=100):
 
 
 
51
  try:
52
  lat = float(latitude)
53
  lng = float(longitude)
 
 
54
 
55
+ if not is_significant_movement(device_id, lat, lng):
56
+ return " Localização recebida (sem movimento significativo)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ # Restante da função permanece igual...
59
+ # ... (código anterior da função add_location)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ # Interface Gradio com atualização automática
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  def create_interface():
63
+ with gr.Blocks(title="📍 Monitor GPS em Tempo Real", theme=gr.themes.Soft()) as demo:
 
 
64
  gr.Markdown("""
65
+ # 📍 Monitoramento GPS em Tempo Real
 
 
66
 
67
+ **Visualize a localização de dispositivos com atualização automática**
68
  """)
69
 
70
  with gr.Tabs():
71
+ with gr.TabItem("🗺️ Mapa em Tempo Real"):
72
+ map_html = gr.HTML(label="Mapa GPS em Tempo Real")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  gr.Markdown("""
74
+ ### Instruções para Celular:
75
+ 1. Acesse este link no seu celular: [Enviar Localização](#)
76
+ 2. Permita o acesso à localização
77
+ 3. Sua posição será atualizada automaticamente
78
  """)
79
+
80
+ # Atualização mais frequente para o mapa
81
+ gr.Timer(10.0, get_updated_map, [], [map_html])
82
 
83
+ with gr.TabItem("📋 Dispositivos"):
84
+ devices_table = gr.Dataframe(label="Dispositivos Conectados")
85
+ gr.Timer(15.0, get_device_list, [], [devices_table])
86
+
87
+ return demo
88
+
89
+ # Código HTML/JS para captura de localização no celular
90
+ mobile_page = """
91
+ <!DOCTYPE html>
92
+ <html>
93
+ <head>
94
+ <title>Enviar Localização</title>
95
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
96
+ <style>
97
+ body { font-family: Arial, sans-serif; padding: 20px; }
98
+ button { padding: 10px 15px; font-size: 16px; }
99
+ </style>
100
+ </head>
101
+ <body>
102
+ <h1>Compartilhar Localização</h1>
103
+ <div id="status">Pressione o botão para começar</div>
104
+ <button id="startBtn">Iniciar Compartilhamento</button>
105
+ <p id="coordinates"></p>
106
+
107
+ <script>
108
+ const deviceId = "celular_" + Math.random().toString(36).substring(2, 8);
109
+ const deviceName = "Celular " + prompt("Digite seu nome:", "Usuário");
110
+ let watchId = null;
111
+
112
+ document.getElementById("startBtn").addEventListener("click", () => {
113
+ if (watchId) {
114
+ navigator.geolocation.clearWatch(watchId);
115
+ watchId = null;
116
+ document.getElementById("status").textContent = "Compartilhamento parado";
117
+ return;
118
+ }
119
 
120
+ document.getElementById("status").textContent = "Obtendo localização...";
 
 
 
 
 
121
 
122
+ watchId = navigator.geolocation.watchPosition(
123
+ (position) => {
124
+ const { latitude, longitude, accuracy } = position.coords;
125
+ document.getElementById("coordinates").textContent =
126
+ `Lat: ${latitude.toFixed(6)}, Long: ${longitude.toFixed(6)} (Precisão: ${accuracy}m)`;
127
+
128
+ // Enviar para o servidor
129
+ fetch("/api/location", {
130
+ method: "POST",
131
+ headers: { "Content-Type": "application/json" },
132
+ body: JSON.stringify({
133
+ device_id: deviceId,
134
+ device_name: deviceName,
135
+ latitude,
136
+ longitude,
137
+ accuracy,
138
+ battery: navigator.getBattery ? (await navigator.getBattery()).level * 100 : 100
139
+ })
140
+ }).then(response => console.log("Localização enviada"));
141
+ },
142
+ (error) => {
143
+ document.getElementById("status").textContent = "Erro: " + error.message;
144
+ },
145
+ {
146
+ enableHighAccuracy: true,
147
+ maximumAge: 10000,
148
+ timeout: 5000
149
+ }
150
+ );
151
 
152
+ document.getElementById("status").textContent = "Compartilhando localização...";
153
+ });
154
+ </script>
155
+ </body>
156
+ </html>
157
+ """
158
+
159
+ # Rota para a página mobile
160
+ @app.get("/mobile")
161
+ async def mobile_client():
162
+ return HTMLResponse(mobile_page)
163
+
164
+ # Executar tanto o Gradio quanto o FastAPI
165
+ def run_app():
166
+ gradio_app = create_interface()
167
+ gradio_app.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
 
169
  if __name__ == "__main__":
170
+ # Iniciar Gradio em uma thread separada
171
+ gradio_thread = Thread(target=run_app)
172
+ gradio_thread.start()
173
+
174
+ # Iniciar FastAPI
175
+ uvicorn.run(app, host="0.0.0.0", port=8000)