dschandra commited on
Commit
3ac3d03
·
verified ·
1 Parent(s): e44653d

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +473 -0
app.py ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ from io import BytesIO
6
+ import os
7
+ import logging
8
+ import base64
9
+ import shutil
10
+ import tempfile
11
+ from simple_salesforce import Salesforce
12
+ from reportlab.lib.pagesizes import letter
13
+ from reportlab.pdfgen import canvas
14
+ from fastapi import FastAPI, Form, File, UploadFile
15
+ from fastapi.middleware.cors import CORSMiddleware
16
+ from fastapi.responses import JSONResponse
17
+ from fastapi.staticfiles import StaticFiles
18
+
19
+ # Configure logging to show detailed messages
20
+ logging.basicConfig(level=logging.DEBUG)
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Salesforce credentials (use environment variables in production)
24
+ SALESFORCE_USERNAME = os.getenv("SALESFORCE_USERNAME", "[email protected]")
25
+ SALESFORCE_PASSWORD = os.getenv("SALESFORCE_PASSWORD", "#Prasad@1926")
26
+ SALESFORCE_SECURITY_TOKEN = os.getenv("SALESFORCE_SECURITY_TOKEN", "4rmMYJ0augm8HKv9lpWXKYkD6")
27
+ SALESFORCE_DOMAIN = os.getenv("SALESFORCE_DOMAIN", "login") # or "test" for sandbox
28
+
29
+ logger.debug(f"Using Salesforce credentials - Username: {SALESFORCE_USERNAME}, Security Token: {SALESFORCE_SECURITY_TOKEN[:5]}...")
30
+
31
+ # Function to authenticate with Salesforce
32
+ def get_salesforce_connection():
33
+ try:
34
+ logger.debug("Attempting to connect to Salesforce...")
35
+ sf = Salesforce(
36
+ username=SALESFORCE_USERNAME,
37
+ password=SALESFORCE_PASSWORD,
38
+ security_token=SALESFORCE_SECURITY_TOKEN,
39
+ domain=SALESFORCE_DOMAIN
40
+ )
41
+ logger.info("Salesforce connection successful.")
42
+ result = sf.query("SELECT Id FROM User LIMIT 1")
43
+ logger.debug(f"Successfully queried Salesforce to confirm connection. Result: {result}")
44
+ return sf
45
+ except Exception as e:
46
+ logger.error(f"Failed to connect to Salesforce: {str(e)}", exc_info=True)
47
+ return None
48
+
49
+ # Function to upload a file to Salesforce as a ContentVersion
50
+ def upload_file_to_salesforce(file_path, file_name, record_id=None):
51
+ try:
52
+ sf = get_salesforce_connection()
53
+ if not sf:
54
+ logger.error("Salesforce connection failed. Cannot upload file.")
55
+ return None
56
+
57
+ with open(file_path, "rb") as f:
58
+ file_data = f.read()
59
+
60
+ encoded_file_data = base64.b64encode(file_data).decode('utf-8')
61
+ logger.debug(f"Uploading file {file_name} for record ID: {record_id}")
62
+ content_version_data = {
63
+ "Title": file_name,
64
+ "PathOnClient": file_name,
65
+ "VersionData": encoded_file_data,
66
+ }
67
+
68
+ if record_id:
69
+ content_version_data["FirstPublishLocationId"] = record_id
70
+
71
+ content_version = sf.ContentVersion.create(content_version_data)
72
+ logger.info(f"File uploaded to Salesforce with ContentVersion ID: {content_version['id']}")
73
+ return content_version["id"]
74
+ except Exception as e:
75
+ logger.error(f"Error uploading file to Salesforce: {str(e)}", exc_info=True)
76
+ return None
77
+
78
+ # Function to generate PDF
79
+ def generate_pdf(record_data):
80
+ try:
81
+ logger.debug("Generating PDF...")
82
+ pdf_file = BytesIO()
83
+ c = canvas.Canvas(pdf_file, pagesize=letter)
84
+ c.drawString(100, 750, f"Project Title: {record_data['project_title']}")
85
+ c.drawString(100, 730, f"Estimated Duration: {record_data['estimated_duration']} days")
86
+ c.drawString(100, 710, f"AI Plan Score: {record_data['ai_plan_score']}%")
87
+ c.drawString(100, 690, f"Status: {record_data['status']}")
88
+ c.drawString(100, 670, f"Risk Tags: {record_data['risk_tags']}")
89
+ c.save()
90
+ pdf_file.seek(0)
91
+ logger.debug("PDF generated successfully.")
92
+ return pdf_file
93
+ except Exception as e:
94
+ logger.error(f"Error generating PDF: {str(e)}", exc_info=True)
95
+ return None
96
+
97
+ # Function to upload PDF to Salesforce and get its URL
98
+ def upload_pdf_to_salesforce(pdf_file, project_title, record_id=None):
99
+ try:
100
+ sf = get_salesforce_connection()
101
+ if not sf:
102
+ logger.error("Salesforce connection failed. Cannot upload PDF.")
103
+ return None, None
104
+
105
+ encoded_pdf_data = base64.b64encode(pdf_file.getvalue()).decode('utf-8')
106
+ logger.debug(f"Uploading PDF for project: {project_title}, record ID: {record_id}")
107
+ content_version_data = {
108
+ "Title": f"{project_title} - Gantt Chart PDF",
109
+ "PathOnClient": f"{project_title}_Gantt_Chart.pdf",
110
+ "VersionData": encoded_pdf_data,
111
+ }
112
+
113
+ if record_id:
114
+ content_version_data["FirstPublishLocationId"] = record_id
115
+
116
+ content_version = sf.ContentVersion.create(content_version_data)
117
+ content_version_id = content_version["id"]
118
+ logger.info(f"PDF uploaded to Salesforce with ContentVersion ID: {content_version_id}")
119
+
120
+ result = sf.query(f"SELECT Id, ContentDocumentId FROM ContentVersion WHERE Id = '{content_version_id}'")
121
+ if not result['records']:
122
+ logger.error("No records returned for ContentVersion query")
123
+ return content_version_id, None
124
+
125
+ content_document_id = result['records'][0]['ContentDocumentId']
126
+ file_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{content_version_id}"
127
+ logger.debug(f"Generated PDF URL: {file_url}")
128
+ return content_version_id, file_url
129
+ except Exception as e:
130
+ logger.error(f"Error uploading PDF to Salesforce: {str(e)}", exc_info=True)
131
+ return None, None
132
+
133
+ # Function to create or update project timeline in Salesforce
134
+ def send_to_salesforce(project_title, gantt_chart_url, ai_plan_score, estimated_duration, status="Draft", record_id=None, location="", weather_type="", work_items=None, work_items_id=None):
135
+ try:
136
+ logger.debug("Starting send_to_salesforce function...")
137
+ sf = get_salesforce_connection()
138
+ if not sf:
139
+ logger.error("Salesforce connection failed. Cannot proceed with record creation/update.")
140
+ return None
141
+
142
+ try:
143
+ obj_description = sf.AI_Project_Timeline__c.describe()
144
+ logger.debug("AI_Project_Timeline__c object exists and is accessible.")
145
+ available_fields = [field['name'] for field in obj_description['fields']]
146
+ logger.debug(f"Available fields on AI_Project_Timeline__c: {available_fields}")
147
+ except Exception as e:
148
+ logger.error(f"Error: AI_Project_Timeline__c object not found or inaccessible: {str(e)}")
149
+ return None
150
+
151
+ sf_data = {
152
+ "Name": project_title[:80],
153
+ "Project_Title__c": project_title,
154
+ "Estimated_Duration__c": estimated_duration,
155
+ "AI_Plan_Score__c": ai_plan_score,
156
+ "Status__c": status,
157
+ "Location__c": location,
158
+ "Weather_Type__c": weather_type,
159
+ }
160
+
161
+ if gantt_chart_url:
162
+ sf_data["Gantt_Chart_PDF__c"] = gantt_chart_url
163
+
164
+ if work_items_id:
165
+ sf_data["Work_Items__c"] = work_items_id
166
+
167
+ logger.debug(f"Prepared Salesforce data: {sf_data}")
168
+
169
+ if record_id:
170
+ try:
171
+ logger.info(f"Attempting to update Salesforce record with ID: {record_id}")
172
+ sf.AI_Project_Timeline__c.update(record_id, sf_data)
173
+ logger.info(f"Successfully updated Salesforce record with ID: {record_id}")
174
+ return record_id
175
+ except Exception as e:
176
+ logger.error(f"Error updating record {record_id}: {str(e)}")
177
+ record_id = None
178
+
179
+ logger.info("Creating new Salesforce record...")
180
+ project_record = sf.AI_Project_Timeline__c.create(sf_data)
181
+ if not project_record.get('id'):
182
+ logger.error("Failed to create record, no ID returned")
183
+ return None
184
+
185
+ new_record_id = project_record['id']
186
+ logger.info(f"Created new Salesforce record with ID: {new_record_id}")
187
+ return new_record_id
188
+
189
+ except Exception as e:
190
+ logger.error(f"Error sending data to Salesforce: {str(e)}", exc_info=True)
191
+ if hasattr(e, 'content') and e.content:
192
+ logger.error(f"Salesforce API response: {e.content}")
193
+ return None
194
+
195
+ # Function to generate Gantt chart
196
+ def generate_project_timeline(boq_file, weather, workforce, location, project_title):
197
+ temp_dir = None
198
+ try:
199
+ logger.debug("Processing BOQ data...")
200
+ if not boq_file:
201
+ raise ValueError("No file uploaded")
202
+
203
+ temp_dir = tempfile.mkdtemp()
204
+ output_filename = f"gantt_chart_{project_title.replace(' ', '')}{id(boq_file)}.png"
205
+ output_path = os.path.join(temp_dir, output_filename)
206
+ logger.debug(f"Gantt chart will be saved to: {output_path}")
207
+
208
+ if hasattr(boq_file, 'name'):
209
+ df = pd.read_csv(boq_file.name)
210
+ else:
211
+ df = pd.read_csv(boq_file)
212
+
213
+ if "Task Name" not in df.columns or "Duration" not in df.columns:
214
+ raise ValueError("CSV must contain 'Task Name' and 'Duration' columns")
215
+
216
+ task_names = df["Task Name"].tolist()
217
+ task_durations = df["Duration"].tolist()
218
+ logger.debug(f"Tasks: {task_names}, Durations: {task_durations}")
219
+
220
+ fig, ax = plt.subplots(figsize=(10, 5))
221
+ ax.barh(task_names, task_durations, color="skyblue")
222
+ ax.set_xlabel("Duration (days)")
223
+ ax.set_title("Project Timeline Gantt Chart")
224
+ fig.savefig(output_path, format="png", bbox_inches="tight")
225
+ plt.close(fig)
226
+
227
+ risk_tags = [
228
+ f"{task} - {'High' if weather == 'rainy' and duration > 5 else 'Low'} Risk (Weather)"
229
+ for task, duration in zip(task_names, task_durations)
230
+ ]
231
+ risk_tags_str = "\n".join(risk_tags)
232
+ logger.debug(f"Generated risk tags: {risk_tags_str}")
233
+ logger.info("Gantt chart and risk tags generated successfully.")
234
+ return output_path, risk_tags_str, temp_dir
235
+ except Exception as e:
236
+ logger.error(f"Error generating project timeline: {str(e)}", exc_info=True)
237
+ if temp_dir and os.path.exists(temp_dir):
238
+ shutil.rmtree(temp_dir)
239
+ return None, str(e), None
240
+
241
+ # Gradio interface function (for Gradio UI)
242
+ def gradio_interface(boq_file, weather, workforce, location, project_title):
243
+ temp_dir = None
244
+ try:
245
+ logger.info("Starting gradio_interface...")
246
+
247
+ if not boq_file:
248
+ return None, "Error: No BOQ file uploaded"
249
+
250
+ file_path, risk_tags, temp_dir = generate_project_timeline(boq_file, weather, workforce, location, project_title)
251
+ if not file_path:
252
+ return None, f"Error: Failed to generate timeline: {risk_tags}"
253
+
254
+ if hasattr(boq_file, 'name'):
255
+ df = pd.read_csv(boq_file.name)
256
+ else:
257
+ df = pd.read_csv(boq_file)
258
+ estimated_duration = sum(df["Duration"])
259
+ ai_plan_score = min(100, max(0, 100 - (estimated_duration / 100)))
260
+ logger.debug(f"Estimated duration: {estimated_duration}, AI plan score: {ai_plan_score}")
261
+
262
+ record_id = send_to_salesforce(
263
+ project_title=project_title,
264
+ gantt_chart_url="",
265
+ ai_plan_score=ai_plan_score,
266
+ estimated_duration=estimated_duration,
267
+ status="Draft",
268
+ record_id=None,
269
+ location=location,
270
+ weather_type=weather
271
+ )
272
+
273
+ if not record_id:
274
+ return None, f"Error: Failed to create Salesforce record - check logs for details\n\nRisk Tags:\n{risk_tags}"
275
+
276
+ boq_file_path = boq_file.name if hasattr(boq_file, 'name') else boq_file
277
+ work_items_id = upload_file_to_salesforce(boq_file_path, "Boq_data.csv", record_id)
278
+ if not work_items_id:
279
+ logger.warning("Failed to upload BOQ file, but proceeding with record creation")
280
+
281
+ record_data = {
282
+ "project_title": project_title,
283
+ "estimated_duration": estimated_duration,
284
+ "ai_plan_score": ai_plan_score,
285
+ "status": "Draft",
286
+ "risk_tags": risk_tags,
287
+ }
288
+ pdf_file = generate_pdf(record_data)
289
+ if not pdf_file:
290
+ logger.warning("Failed to generate PDF, but proceeding with record creation")
291
+
292
+ pdf_content_id, pdf_url = None, None
293
+ if pdf_file:
294
+ pdf_content_id, pdf_url = upload_pdf_to_salesforce(pdf_file, project_title, record_id)
295
+ if not pdf_content_id:
296
+ logger.warning("Failed to upload PDF, but proceeding with record creation")
297
+
298
+ update_result = send_to_salesforce(
299
+ project_title=project_title,
300
+ gantt_chart_url=pdf_url if pdf_url else "",
301
+ ai_plan_score=ai_plan_score,
302
+ estimated_duration=estimated_duration,
303
+ status="Draft",
304
+ record_id=record_id,
305
+ location=location,
306
+ weather_type=weather,
307
+ work_items_id=work_items_id if work_items_id else ""
308
+ )
309
+ if not update_result:
310
+ logger.warning("Failed to update record with PDF URL, but record was created")
311
+
312
+ # Upload Gantt chart image to Salesforce for a public URL
313
+ image_content_id = upload_file_to_salesforce(file_path, f"{project_title}_Gantt_Chart.png", record_id)
314
+ image_url = None
315
+ if image_content_id:
316
+ sf = get_salesforce_connection()
317
+ if sf:
318
+ image_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{image_content_id}"
319
+ logger.debug(f"Generated image URL: {image_url}")
320
+
321
+ logger.info("Gradio interface completed successfully.")
322
+ return image_url if image_url else file_path, f"Successfully created Salesforce record ID: {record_id}\n\nRisk Tags:\n{risk_tags}"
323
+ except Exception as e:
324
+ logger.error(f"Error in Gradio interface: {str(e)}", exc_info=True)
325
+ return None, f"Error in Gradio interface: {str(e)}"
326
+ finally:
327
+ if temp_dir and os.path.exists(temp_dir):
328
+ shutil.rmtree(temp_dir)
329
+ logger.debug(f"Cleaned up temporary directory: {temp_dir}")
330
+
331
+ # Create a FastAPI app and mount the Gradio app with CORS support
332
+ app = FastAPI()
333
+ app.add_middleware(
334
+ CORSMiddleware,
335
+ allow_origins=["https://aiplannerforcivilworktimel2-dev-ed.develop.lightning.force.com"],
336
+ allow_credentials=True,
337
+ allow_methods=["*"],
338
+ allow_headers=["*"],
339
+ )
340
+ app.mount("/static", StaticFiles(directory=tempfile.gettempdir()), name="static")
341
+
342
+ # FastAPI endpoint for Salesforce controller (defined before mounting Gradio)
343
+ @app.post("/api/gradio_interface")
344
+ async def api_gradio_interface(
345
+ boq_file: UploadFile = File(...),
346
+ weather: str = Form(...),
347
+ workforce: int = Form(...),
348
+ location: str = Form(...),
349
+ project_title: str = Form(...)
350
+ ):
351
+ temp_dir = None
352
+ try:
353
+ logger.info("Starting api_gradio_interface...")
354
+
355
+ # Save uploaded file to temporary path
356
+ temp_dir = tempfile.mkdtemp()
357
+ boq_file_path = os.path.join(temp_dir, boq_file.filename)
358
+ with open(boq_file_path, "wb") as f:
359
+ f.write(boq_file.file.read())
360
+
361
+ file_path, risk_tags, temp_dir = generate_project_timeline(boq_file_path, weather, workforce, location, project_title)
362
+ if not file_path:
363
+ return JSONResponse({"error": f"Failed to generate timeline: {risk_tags}"}, status_code=400)
364
+
365
+ df = pd.read_csv(boq_file_path)
366
+ estimated_duration = sum(df["Duration"])
367
+ ai_plan_score = min(100, max(0, 100 - (estimated_duration / 100)))
368
+ logger.debug(f"Estimated duration: {estimated_duration}, AI plan score: {ai_plan_score}")
369
+
370
+ record_id = send_to_salesforce(
371
+ project_title=project_title,
372
+ gantt_chart_url="",
373
+ ai_plan_score=ai_plan_score,
374
+ estimated_duration=estimated_duration,
375
+ status="Draft",
376
+ record_id=None,
377
+ location=location,
378
+ weather_type=weather
379
+ )
380
+
381
+ if not record_id:
382
+ return JSONResponse({
383
+ "error": f"Failed to create Salesforce record - check logs for details",
384
+ "text": f"Risk Tags:\n{risk_tags}"
385
+ }, status_code=500)
386
+
387
+ work_items_id = upload_file_to_salesforce(boq_file_path, "Boq_data.csv", record_id)
388
+ if not work_items_id:
389
+ logger.warning("Failed to upload BOQ file, but proceeding with record creation")
390
+
391
+ record_data = {
392
+ "project_title": project_title,
393
+ "estimated_duration": estimated_duration,
394
+ "ai_plan_score": ai_plan_score,
395
+ "status": "Draft",
396
+ "risk_tags": risk_tags,
397
+ }
398
+ pdf_file = generate_pdf(record_data)
399
+ if not pdf_file:
400
+ logger.warning("Failed to generate PDF, but proceeding with record creation")
401
+
402
+ pdf_content_id, pdf_url = None, None
403
+ if pdf_file:
404
+ pdf_content_id, pdf_url = upload_pdf_to_salesforce(pdf_file, project_title, record_id)
405
+ if not pdf_content_id:
406
+ logger.warning("Failed to upload PDF, but proceeding with record creation")
407
+
408
+ update_result = send_to_salesforce(
409
+ project_title=project_title,
410
+ gantt_chart_url=pdf_url if pdf_url else "",
411
+ ai_plan_score=ai_plan_score,
412
+ estimated_duration=estimated_duration,
413
+ status="Draft",
414
+ record_id=record_id,
415
+ location=location,
416
+ weather_type=weather,
417
+ work_items_id=work_items_id if work_items_id else ""
418
+ )
419
+ if not update_result:
420
+ logger.warning("Failed to update record with PDF URL, but record was created")
421
+
422
+ image_content_id = upload_file_to_salesforce(file_path, f"{project_title}_Gantt_Chart.png", record_id)
423
+ image_url = None
424
+ if image_content_id:
425
+ sf = get_salesforce_connection()
426
+ if sf:
427
+ image_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{image_content_id}"
428
+ logger.debug(f"Generated image URL: {image_url}")
429
+
430
+ logger.info("API gradio interface completed successfully.")
431
+ return JSONResponse({
432
+ "image": image_url if image_url else f"/static/{os.path.basename(file_path)}",
433
+ "text": f"Successfully created Salesforce record ID: {record_id}\n\nRisk Tags:\n{risk_tags}"
434
+ })
435
+ except Exception as e:
436
+ logger.error(f"Error in API gradio interface: {str(e)}", exc_info=True)
437
+ return JSONResponse({"error": f"Error in API gradio interface: {str(e)}"}, status_code=500)
438
+ finally:
439
+ if temp_dir and os.path.exists(temp_dir):
440
+ shutil.rmtree(temp_dir)
441
+ logger.debug(f"Cleaned up temporary directory: {temp_dir}")
442
+
443
+ # Create Gradio interface
444
+ demo = gr.Blocks(theme="default")
445
+ with demo:
446
+ gr.Markdown("## AI Civil Work Planner")
447
+ gr.Markdown("Generate a project timeline (Gantt chart) and risk tags based on BOQ data and site parameters.")
448
+
449
+ with gr.Row():
450
+ with gr.Column():
451
+ boq_file = gr.File(label="Upload BOQ Data (CSV format)")
452
+ weather = gr.Dropdown(label="Weather", choices=["sunny", "rainy", "cloudy"], value="sunny")
453
+ workforce = gr.Number(label="Workforce Size", value=10, precision=0)
454
+ location = gr.Textbox(label="Location", placeholder="Enter project location")
455
+ project_title = gr.Textbox(label="Project Title", placeholder="Enter project title")
456
+ submit_btn = gr.Button("Generate Timeline")
457
+
458
+ with gr.Column():
459
+ output_image = gr.Image(label="Gantt Chart")
460
+ risk_tags = gr.Textbox(label="Risk Tags and Salesforce Status")
461
+
462
+ submit_btn.click(
463
+ fn=gradio_interface,
464
+ inputs=[boq_file, weather, workforce, location, project_title],
465
+ outputs=[output_image, risk_tags],
466
+ )
467
+
468
+ # Mount Gradio app (use a different variable to avoid overwriting 'app')
469
+ gradio_app = gr.mount_gradio_app(app, demo, path="/")
470
+
471
+ if __name__ == "__main__":
472
+ import uvicorn
473
+ uvicorn.run(gradio_app, host="0.0.0.0", port=7860)