ghostai1 commited on
Commit
7e988c5
Β·
verified Β·
1 Parent(s): aa4b9e2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +356 -3
app.py CHANGED
@@ -2,7 +2,7 @@
2
  # FILE: app.py
3
  # Description: Image-to-Video generation server with Gradio UI and FastAPI for Hugging Face Spaces
4
  # Version: 1.2.8
5
- # Timestamp: 2025-07-01 20:34 CDT
6
  # Author: Grok 3, built by xAI (based on GhostAI's ghostpack_gradio_f1.py)
7
  # NOTE: Optimized for Hugging Face Spaces with H200 GPU, 25 min/day render time
8
  # Loads models from Hugging Face Hub to avoid HDD costs
@@ -623,7 +623,7 @@ async def stop_render(job_id: str, api_key: str = Depends(verify_api_key)):
623
  stream.stop()
624
  active_jobs.pop(job_id, None)
625
  job_status[job_id]["status"] = "stopped"
626
- job_status[job_id]["progress"] = 0.0
627
  logger.info(f"Stopped job {job_id}")
628
  print(f"{yellow(f'Stopped job {job_id}')}")
629
  return JSONResponse(content={"message": f"Job {job_id} stopped"})
@@ -1024,4 +1024,357 @@ def process(img, prm, npr, sd, sec, win, stp, cfg, gsc, rsc, kee, tea, crf, disa
1024
  yield None, None, "", "", gr.update(interactive=False), gr.update(interactive=True)
1025
  stream = AsyncStream()
1026
  jid = str(uuid.uuid4())
1027
- async_run(worker, img, prm, npr, sd, sec, win,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  # FILE: app.py
3
  # Description: Image-to-Video generation server with Gradio UI and FastAPI for Hugging Face Spaces
4
  # Version: 1.2.8
5
+ # Timestamp: 2025-07-01 20:41 CDT
6
  # Author: Grok 3, built by xAI (based on GhostAI's ghostpack_gradio_f1.py)
7
  # NOTE: Optimized for Hugging Face Spaces with H200 GPU, 25 min/day render time
8
  # Loads models from Hugging Face Hub to avoid HDD costs
 
623
  stream.stop()
624
  active_jobs.pop(job_id, None)
625
  job_status[job_id]["status"] = "stopped"
626
+ job_status[jid]["progress"] = 0.0
627
  logger.info(f"Stopped job {job_id}")
628
  print(f"{yellow(f'Stopped job {job_id}')}")
629
  return JSONResponse(content={"message": f"Job {job_id} stopped"})
 
1024
  yield None, None, "", "", gr.update(interactive=False), gr.update(interactive=True)
1025
  stream = AsyncStream()
1026
  jid = str(uuid.uuid4())
1027
+ async_run(worker, img, prm, npr, sd, sec, win, stp, cfg, gsc, rsc, kee, tea, crf, disable_prompt_mods, link_steps_window, stream, jid)
1028
+ out, log = None, ""
1029
+ try:
1030
+ while True:
1031
+ flag, data = stream.output_queue.next()
1032
+ if job_status.get(jid, {}).get("status") == "complete":
1033
+ break
1034
+ if flag == "file":
1035
+ out = data
1036
+ yield out, gr.update(), gr.update(), log, gr.update(interactive=False), gr.update(interactive=True)
1037
+ if flag == "progress":
1038
+ pv, desc, html = data
1039
+ log = desc
1040
+ yield gr.update(), gr.update(visible=True, value=pv), desc, html, gr.update(interactive=False), gr.update(interactive=True)
1041
+ if flag == "complete":
1042
+ yield data, gr.update(visible=False), "Generation complete", "", gr.update(interactive=True), gr.update(interactive=False)
1043
+ break
1044
+ if flag == "end":
1045
+ yield out, gr.update(visible=False), f"Error: {data}", "", gr.update(interactive=True), gr.update(interactive=False)
1046
+ break
1047
+ except Exception as e:
1048
+ logger.error(f"Process loop failed: {e}")
1049
+ yield out, gr.update(visible=False), f"Error: {str(e)}", "", gr.update(interactive=True), gr.update(interactive=False)
1050
+ job_status[jid]["status"] = "error"
1051
+ finally:
1052
+ clear_queue(stream.input_queue)
1053
+ clear_queue(stream.output_queue)
1054
+ torch.cuda.empty_cache()
1055
+
1056
+ def end_process():
1057
+ global stream
1058
+ if stream:
1059
+ stream.input_queue.push("end")
1060
+ logger.info("Gradio: Render stop requested")
1061
+ print(f"{red('Gradio: Render stop requested')}")
1062
+
1063
+ # Gradio UI (same as original)
1064
+ quick_prompts = [
1065
+ ["Smooth animation: A character waves for 3 seconds, then stands still for 2 seconds, static camera, silent."],
1066
+ ["Smooth animation: A character moves for 5 seconds, static camera, silent."]
1067
+ ]
1068
+ css = make_progress_bar_css() + """
1069
+ .orange-button{background:#ff6200;color:#fff;border-color:#ff6200;}
1070
+ .load-button{background:#4CAF50;color:#fff;border-color:#4CAF50;margin-left:10px;}
1071
+ .big-setting-button{background:#0066cc;color:#fff;border:none;padding:14px 24px;font-size:18px;width:100%;border-radius:6px;margin:8px 0;}
1072
+ .styled-dropdown{width:250px;padding:5px;border-radius:4px;}
1073
+ .viewer-column{width:100%;max-width:900px;margin:0 auto;}
1074
+ .media-preview img,.media-preview video{max-width:100%;height:380px;object-fit:contain;border:1px solid #444;border-radius:6px;}
1075
+ .media-container{display:flex;gap:20px;align-items:flex-start;}
1076
+ .control-box{min-width:220px;}
1077
+ .control-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
1078
+ .image-gallery{display:grid!important;grid-template-columns:repeat(auto-fit,minmax(300px,1fr))!important;gap:10px;padding:10px!important;overflow-y:auto!important;max-height:360px!important;}
1079
+ .image-gallery .gallery-item{padding:10px;height:360px!important;width:300px!important;}
1080
+ .image-gallery img{object-fit:contain;height:360px!important;width:300px!important;}
1081
+ .video-gallery{display:grid!important;grid-template-columns:repeat(auto-fit,minmax(300px,1fr))!important;gap:10px;padding:10px!important;overflow-y:auto!important;max-height:360px!important;}
1082
+ .video-gallery .gallery-item{padding:10px;height:360px!important;width:300px!important;}
1083
+ .video-gallery video{object-fit:contain;height:360px!important;width:300px!important;}
1084
+ .stop-button {background-color: #ff4d4d !important; color: white !important;}
1085
+ """
1086
+
1087
+ blk = gr.Blocks(css=css, title="GhostPack F1 Pro").queue()
1088
+ with blk:
1089
+ gr.Markdown("# πŸ‘» GhostPack F1 Pro")
1090
+ with gr.Tabs():
1091
+ with gr.TabItem("πŸ‘» Generate"):
1092
+ with gr.Row():
1093
+ with gr.Column():
1094
+ img_in = gr.Image(sources="upload", type="numpy", label="Image", height=320)
1095
+ generate_button = gr.Button("Generate Video", elem_id="generate_button")
1096
+ stop_button = gr.Button("Stop Generation", elem_id="stop_button", elem_classes="stop-button")
1097
+ prm = gr.Textbox(
1098
+ label="Prompt",
1099
+ value="Smooth animation: A female stands with subtle, sensual micro-movements, breathing gently, slight head tilt, static camera, silent",
1100
+ elem_id="prompt_input",
1101
+ )
1102
+ npr = gr.Textbox(
1103
+ label="Negative Prompt",
1104
+ value="low quality, blurry, speaking, talking, moaning, vocalizing, lip movement, mouth animation, sound, dialogue, speech, whispering, shouting, lip sync, facial animation, expressive face, verbal expression, animated mouth",
1105
+ elem_id="negative_prompt_input",
1106
+ )
1107
+ save_msg = gr.Markdown("")
1108
+ disable_prompt_mods = gr.Checkbox(label="Disable Prompt Modifications", value=False)
1109
+ link_steps_window = gr.Checkbox(label="Link Steps and Latent Window", value=True)
1110
+ btn_save = gr.Button("Save Prompt")
1111
+ btn1, btn2, btn3 = (
1112
+ gr.Button("Load Most Recent"),
1113
+ gr.Button("Load 2nd Recent"),
1114
+ gr.Button("Load 3rd Recent"),
1115
+ )
1116
+ ds = gr.Dataset(samples=quick_prompts, label="Quick List", components=[prm])
1117
+ ds.click(lambda x: x[0], [ds], [prm])
1118
+ btn_save.click(save_prompt_fn, [prm, npr], [save_msg])
1119
+ btn1.click(lambda: load_prompt_fn(0), [], [prm])
1120
+ btn2.click(lambda: load_prompt_fn(1), [], [prm])
1121
+ btn3.click(lambda: load_prompt_fn(2), [], [prm])
1122
+ camera_action_input = gr.Dropdown(
1123
+ choices=[
1124
+ "Static Camera", "Slight Orbit Left", "Slight Orbit Right",
1125
+ "Slight Orbit Up", "Slight Orbit Down", "Top-Down View",
1126
+ "Slight Zoom In", "Slight Zoom Out",
1127
+ ],
1128
+ label="Camera Action",
1129
+ value="Static Camera",
1130
+ elem_id="camera_action_input",
1131
+ info="Select a camera movement to append to the prompt.",
1132
+ )
1133
+ camera_action_input.change(
1134
+ fn=lambda prompt, camera_action: update_prompt(prompt, camera_action),
1135
+ inputs=[prm, camera_action_input],
1136
+ outputs=prm,
1137
+ )
1138
+ with gr.Column():
1139
+ pv = gr.Image(label="Next Latents", height=200, visible=False)
1140
+ vid = gr.Video(label="Finished", autoplay=True, height=500, loop=True, show_share_button=False)
1141
+ log_md = gr.Markdown("")
1142
+ bar = gr.HTML("")
1143
+ with gr.Column():
1144
+ se = gr.Number(label="Seed", value=31337, precision=0, elem_id="seed_input")
1145
+ sec = gr.Slider(label="Video Length (s)", minimum=1, maximum=10, value=8.0, step=0.1, elem_id="video_length_input")
1146
+ win = gr.Slider(label="Latent Window", minimum=1, maximum=10, value=3, step=1, elem_id="latent_window_input")
1147
+ stp = gr.Slider(label="Steps", minimum=1, maximum=100, value=12, step=1, elem_id="steps_input")
1148
+ cfg = gr.Slider(label="CFG", minimum=1, maximum=32, value=1.7, step=0.01, elem_id="cfg_input")
1149
+ gsc = gr.Slider(label="Distilled CFG", minimum=1, maximum=32, value=4.0, step=0.01, elem_id="distilled_cfg_input")
1150
+ rsc = gr.Slider(label="CFG Re-Scale", minimum=0, maximum=1, value=0.5, step=0.01, elem_id="cfg_rescale_input")
1151
+ kee = gr.Slider(label="GPU Keep (GB)", minimum=6, maximum=free_mem, value=6.5, step=0.1, elem_id="gpu_keep_input")
1152
+ crf = gr.Slider(label="MP4 CRF", minimum=0, maximum=100, value=20, step=1, elem_id="mp4_crf_input")
1153
+ tea = gr.Checkbox(label="Use TeaCache", value=True, elem_id="use_teacache_input")
1154
+ generate_button.click(
1155
+ fn=process,
1156
+ inputs=[img_in, prm, npr, se, sec, win, stp, cfg, gsc, rsc, kee, tea, crf, disable_prompt_mods, link_steps_window],
1157
+ outputs=[vid, pv, log_md, bar, generate_button, stop_button],
1158
+ )
1159
+ stop_button.click(fn=end_process)
1160
+ gr.Button("Update Progress").click(fn=lambda: get_progress(), outputs=[log_md, bar])
1161
+
1162
+ with gr.TabItem("πŸ–ΌοΈ Image Gallery"):
1163
+ with gr.Row(elem_classes="media-container"):
1164
+ with gr.Column(scale=3):
1165
+ image_preview = gr.Image(
1166
+ label="Viewer", value=(list_images()[0] if list_images() else None),
1167
+ interactive=False, elem_classes="media-preview",
1168
+ )
1169
+ with gr.Column(elem_classes="control-box"):
1170
+ image_dropdown = gr.Dropdown(
1171
+ choices=[os.path.basename(i) for i in list_images()],
1172
+ value=(os.path.basename(list_images()[0]) if list_images() else None),
1173
+ label="Select", elem_classes="styled-dropdown",
1174
+ )
1175
+ with gr.Row(elem_classes="control-grid"):
1176
+ load_btn = gr.Button("Load", elem_classes="load-button")
1177
+ next_btn = gr.Button("Next", elem_classes="load-button")
1178
+ with gr.Row(elem_classes="control-grid"):
1179
+ refresh_btn = gr.Button("Refresh")
1180
+ delete_btn = gr.Button("Delete", elem_classes="orange-button")
1181
+ image_gallery = gr.Gallery(
1182
+ value=list_images(), label="Thumbnails", columns=6, height=360,
1183
+ allow_preview=False, type="filepath", elem_classes="image-gallery",
1184
+ )
1185
+ load_btn.click(load_image, [image_dropdown], [image_preview, image_dropdown])
1186
+ next_btn.click(next_image_and_load, [image_dropdown], [image_preview, image_dropdown])
1187
+ refresh_btn.click(
1188
+ lambda: (
1189
+ gr.update(choices=[os.path.basename(i) for i in list_images()], value=os.path.basename(list_images()[0]) if list_images() else None),
1190
+ gr.update(value=list_images()[0] if list_images() else None),
1191
+ gr.update(value=list_images()),
1192
+ ),
1193
+ [], [image_dropdown, image_preview, image_gallery],
1194
+ )
1195
+ delete_btn.click(
1196
+ lambda sel: (
1197
+ os.remove(os.path.join(VIDEO_IMG_DIR, sel)) if sel and os.path.exists(os.path.join(VIDEO_IMG_DIR, sel)) else None
1198
+ ) or load_image(""),
1199
+ [image_dropdown], [image_preview, image_dropdown],
1200
+ )
1201
+ image_gallery.select(gallery_image_select, [], [image_preview, image_dropdown])
1202
+
1203
+ with gr.TabItem("🎬 Video Gallery"):
1204
+ with gr.Row(elem_classes="media-container"):
1205
+ with gr.Column(scale=3):
1206
+ video_preview = gr.Video(
1207
+ label="Viewer", value=(list_videos()[0] if list_videos() else None),
1208
+ autoplay=True, loop=True, interactive=False, elem_classes="media-preview",
1209
+ )
1210
+ with gr.Column(elem_classes="control-box"):
1211
+ video_dropdown = gr.Dropdown(
1212
+ choices=[os.path.basename(v) for v in list_videos()],
1213
+ value=(os.path.basename(list_videos()[0]) if list_videos() else None),
1214
+ label="Select", elem_classes="styled-dropdown",
1215
+ )
1216
+ with gr.Row(elem_classes="control-grid"):
1217
+ load_vbtn = gr.Button("Load", elem_classes="load-button")
1218
+ next_vbtn = gr.Button("Next", elem_classes="load-button")
1219
+ with gr.Row(elem_classes="control-grid"):
1220
+ refresh_v = gr.Button("Refresh")
1221
+ delete_v = gr.Button("Delete", elem_classes="orange-button")
1222
+ video_gallery = gr.Gallery(
1223
+ value=list_videos(), label="Thumbnails", columns=6, height=360,
1224
+ allow_preview=False, type="filepath", elem_classes="video-gallery",
1225
+ )
1226
+ load_vbtn.click(load_video, [video_dropdown], [video_preview, video_dropdown])
1227
+ next_vbtn.click(next_video_and_load, [video_dropdown], [video_preview, video_dropdown])
1228
+ refresh_v.click(
1229
+ lambda: (
1230
+ gr.update(choices=[os.path.basename(v) for v in list_videos()], value=os.path.basename(list_videos()[0]) if list_videos() else None),
1231
+ gr.update(value=list_videos()[0] if list_videos() else None),
1232
+ gr.update(value=list_videos()),
1233
+ ),
1234
+ [], [video_dropdown, video_preview, video_gallery],
1235
+ )
1236
+ delete_v.click(
1237
+ lambda sel: (
1238
+ os.remove(os.path.join(VIDEO_OUTPUT_DIR, sel)) if sel and os.path.exists(os.path.join(VIDEO_OUTPUT_DIR, sel)) else None
1239
+ ) or load_video(""),
1240
+ [video_dropdown], [video_preview, video_dropdown],
1241
+ )
1242
+ video_gallery.select(gallery_video_select, [], [video_preview, video_dropdown])
1243
+
1244
+ with gr.TabItem("πŸ‘» About"):
1245
+ gr.Markdown("## GhostPack F1 Pro")
1246
+ with gr.Row():
1247
+ with gr.Column():
1248
+ gr.Markdown("**πŸ› οΈ Description**\nImage-to-Video toolkit powered by HunyuanVideo & FramePack-F1")
1249
+ with gr.Column():
1250
+ gr.Markdown(f"**πŸ“¦ Version**\n{VERSION}")
1251
+ with gr.Column():
1252
+ gr.Markdown("**✍️ Author**\nGhostAI")
1253
+ with gr.Column():
1254
+ gr.Markdown("**πŸ”— Repo**\nhttps://huggingface.co/spaces/ghostai1/GhostPack")
1255
+
1256
+ with gr.TabItem("βš™οΈ Settings"):
1257
+ ct = gr.Button("Clear Temp", elem_classes="big-setting-button")
1258
+ ctmsg = gr.Markdown("")
1259
+ co = gr.Button("Clear Old", elem_classes="big-setting-button")
1260
+ comsg = gr.Markdown("")
1261
+ ci = gr.Button("Clear Images", elem_classes="big-setting-button")
1262
+ cimg = gr.Markdown("")
1263
+ cv = gr.Button("Clear Videos", elem_classes="big-setting-button")
1264
+ cvid = gr.Markdown("")
1265
+ ct.click(clear_temp_videos, [], ctmsg)
1266
+ co.click(clear_old_files, [], comsg)
1267
+ ci.click(clear_images, [], cimg)
1268
+ cv.click(clear_videos, [], cvid)
1269
+
1270
+ with gr.TabItem("πŸ› οΈ Install"):
1271
+ xs = gr.Textbox(value=status_xformers(), interactive=False, label="xformers")
1272
+ bx = gr.Button("Install xformers", elem_classes="big-setting-button")
1273
+ ss = gr.Textbox(value=status_sage(), interactive=False, label="sage-attn")
1274
+ bs = gr.Button("Install sage-attn", elem_classes="big-setting-button")
1275
+ fs = gr.Textbox(value=status_flash(), interactive=False, label="flash-attn")
1276
+ bf = gr.Button("Install flash-attn", elem_classes="big-setting-button")
1277
+ cs = gr.Textbox(value=status_colorama(), interactive=False, label="colorama")
1278
+ bc = gr.Button("Install colorama", elem_classes="big-setting-button")
1279
+ bx.click(install_xformers, [], xs)
1280
+ bs.click(install_sage_attn, [], ss)
1281
+ bf.click(install_flash_attn, [], fs)
1282
+ bc.click(install_colorama, [], cs)
1283
+
1284
+ with gr.TabItem("πŸ“œ Logs"):
1285
+ logs = gr.Textbox(lines=20, interactive=False, label="Install Logs")
1286
+ rl = gr.Button("Refresh", elem_classes="big-setting-button")
1287
+ cl = gr.Button("Clear", elem_classes="big-setting-button")
1288
+ rl.click(refresh_logs, [], logs)
1289
+ cl.click(clear_logs, [], logs)
1290
+
1291
+ gr.HTML(
1292
+ """
1293
+ <script>
1294
+ document.querySelectorAll('.video-gallery video').forEach(v => {
1295
+ v.addEventListener('loadedmetadata', () => {
1296
+ if (v.duration > 2) v.currentTime = 2;
1297
+ });
1298
+ });
1299
+ </script>
1300
+ """
1301
+ )
1302
+
1303
+ def update_prompt(prompt, camera_action):
1304
+ camera_actions = [
1305
+ "static camera", "slight camera orbit left", "slight camera orbit right",
1306
+ "slight camera orbit up", "slight camera orbit down", "top-down view",
1307
+ "slight camera zoom in", "slight camera zoom out",
1308
+ ]
1309
+ for action in camera_actions:
1310
+ prompt = re.sub(rf",\s*{re.escape(action)}\b", "", prompt, flags=re.IGNORECASE).strip()
1311
+ if camera_action and camera_action != "None":
1312
+ camera_phrase = f", {camera_action.lower()}"
1313
+ if len(prompt.split()) + len(camera_phrase.split()) <= 50:
1314
+ return prompt + camera_phrase
1315
+ else:
1316
+ logger.warning(f"Prompt exceeds 50 words after adding camera action: {prompt}")
1317
+ print(f"{yellow(f'API: Warning: Prompt exceeds 50 words with camera action')}")
1318
+ return prompt
1319
+
1320
+ def get_progress():
1321
+ return f"Status: {job_status.get('latest', {'status': 'idle'})['status']}\nProgress: {job_status.get('latest', {'progress': 0.0})['progress']:.1f}%\nLast Render Time: {job_status.get('latest', {'render_time': 0})['render_time']:.1f}s"
1322
+
1323
+ # Check for port conflicts
1324
+ if is_port_in_use(args.port):
1325
+ logger.error(f"Port {args.port} is already in use")
1326
+ print(f"{red(f'Error: Port {args.port} is already in use. Please stop other instances or change ports.')}")
1327
+ sys.exit(1)
1328
+
1329
+ # Run FastAPI and optional Gradio
1330
+ def run_api():
1331
+ try:
1332
+ logger.info(f"Starting FastAPI on {args.server}:{args.port}")
1333
+ print(f"{green(f'Starting FastAPI on {args.server}:{args.port}')}")
1334
+ uvicorn.run(app, host=args.server, port=args.port)
1335
+ except Exception as e:
1336
+ logger.error(f"Failed to start FastAPI: {e}", exc_info=True)
1337
+ print(f"{red(f'Error: Failed to start FastAPI: {e}')}")
1338
+ sys.exit(1)
1339
+
1340
+ if __name__ == "__main__":
1341
+ try:
1342
+ logger.info(f"Starting GhostPack F1 Pro Server version {VERSION}")
1343
+ print(f"Starting GhostPack F1 Pro Server version {VERSION}")
1344
+ api_thread = Thread(target=run_api)
1345
+ api_thread.daemon = True
1346
+ api_thread.start()
1347
+ time.sleep(5)
1348
+ try:
1349
+ response = requests.get(f"http://{args.server}:{args.port}/health", timeout=10)
1350
+ if response.status_code != 200:
1351
+ raise RuntimeError("FastAPI health check failed")
1352
+ logger.info("FastAPI health check passed")
1353
+ print(f"{green('FastAPI health check passed')}")
1354
+ except Exception as e:
1355
+ logger.error(f"FastAPI not ready: {e}")
1356
+ print(f"{red(f'Error: FastAPI not ready: {e}')}")
1357
+ sys.exit(1)
1358
+
1359
+ if args.gradio:
1360
+ logger.info(f"Starting Gradio UI on {args.server}:7860")
1361
+ print(f"{green(f'Starting Gradio UI on {args.server}:7860')}")
1362
+ server = blk.launch(
1363
+ server_name=args.server,
1364
+ server_port=7860,
1365
+ share=args.share,
1366
+ inbrowser=args.inbrowser,
1367
+ prevent_thread_lock=True,
1368
+ allowed_paths=["/"]
1369
+ )
1370
+ if args.share and server.share_url:
1371
+ logger.info(f"Public Gradio URL: {server.share_url}")
1372
+ print(f"{yellow(f'Public Gradio URL: {server.share_url}')}")
1373
+ logger.info(f"Gradio UI running on http://{args.server}:7860")
1374
+ print(f"{green(f'Gradio UI running on http://{args.server}:7860')}")
1375
+ while True:
1376
+ time.sleep(1)
1377
+ except KeyboardInterrupt:
1378
+ logger.info("Shutting down gracefully")
1379
+ print(f"{green('Shutting down gracefully')}")
1380
+ sys.exit(0)