Update gradio_app.py
Browse files- gradio_app.py +198 -39
gradio_app.py
CHANGED
|
@@ -3,6 +3,8 @@ import gradio as gr
|
|
| 3 |
import asyncio
|
| 4 |
import uuid
|
| 5 |
import threading
|
|
|
|
|
|
|
| 6 |
from datetime import datetime
|
| 7 |
import logging
|
| 8 |
import traceback
|
|
@@ -71,7 +73,6 @@ def delete_job(job_id):
|
|
| 71 |
try:
|
| 72 |
# Remove the entire output directory for this job
|
| 73 |
output_dir = os.path.dirname(job['output_file'])
|
| 74 |
-
import shutil
|
| 75 |
shutil.rmtree(output_dir, ignore_errors=True)
|
| 76 |
except Exception as e:
|
| 77 |
logger.error(f"Error removing output files: {e}")
|
|
@@ -722,29 +723,92 @@ with gr.Blocks(
|
|
| 722 |
)
|
| 723 |
|
| 724 |
with gr.Tab("π Job History & Management"):
|
| 725 |
-
|
| 726 |
-
with gr.Column(scale=3):
|
| 727 |
-
refresh_jobs_btn = gr.Button("π Refresh Job List", variant="secondary")
|
| 728 |
-
with gr.Column(scale=1):
|
| 729 |
-
clear_completed_btn = gr.Button("π§Ή Clear Completed Jobs", variant="secondary")
|
| 730 |
-
clear_all_btn = gr.Button("ποΈ Clear All Jobs", variant="stop")
|
| 731 |
-
|
| 732 |
jobs_table = gr.Dataframe(
|
| 733 |
headers=["ID", "Topic", "Status", "Progress (%)", "Start Time", "Message"],
|
| 734 |
datatype=["str", "str", "str", "number", "str", "str"],
|
| 735 |
interactive=False,
|
| 736 |
-
label=
|
| 737 |
-
wrap=True
|
|
|
|
| 738 |
)
|
| 739 |
-
|
| 740 |
with gr.Row():
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
with gr.Tab("βΉοΈ Help & Documentation"):
|
| 749 |
gr.Markdown("""
|
| 750 |
## π― How to Use Theory2Manim
|
|
@@ -1027,6 +1091,7 @@ with gr.Blocks(
|
|
| 1027 |
)
|
| 1028 |
|
| 1029 |
# Job history tab functions
|
|
|
|
| 1030 |
def load_job_list():
|
| 1031 |
jobs = get_job_list()
|
| 1032 |
rows = []
|
|
@@ -1040,33 +1105,111 @@ with gr.Blocks(
|
|
| 1040 |
formatted_time = start_time
|
| 1041 |
else:
|
| 1042 |
formatted_time = 'Unknown'
|
| 1043 |
-
|
| 1044 |
rows.append([
|
| 1045 |
-
job['id'][:8] + '...',
|
| 1046 |
-
job['topic'][:50] + ('...' if len(job['topic']) > 50 else ''),
|
| 1047 |
-
job['status'].title(),
|
| 1048 |
-
job['progress'],
|
| 1049 |
formatted_time,
|
| 1050 |
job['message'][:100] + ('...' if len(job['message']) > 100 else '')
|
| 1051 |
])
|
| 1052 |
return rows
|
| 1053 |
|
| 1054 |
def select_job(evt: gr.EventData):
|
| 1055 |
-
if not evt:
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
selected_row = evt.index[0]
|
| 1059 |
jobs = get_job_list()
|
| 1060 |
if selected_row < len(jobs):
|
| 1061 |
-
|
| 1062 |
-
|
|
|
|
| 1063 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1064 |
def delete_selected_job(job_id):
|
| 1065 |
-
|
| 1066 |
-
|
| 1067 |
-
return
|
| 1068 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1069 |
|
|
|
|
| 1070 |
refresh_jobs_btn.click(
|
| 1071 |
fn=load_job_list,
|
| 1072 |
outputs=[jobs_table]
|
|
@@ -1077,17 +1220,31 @@ with gr.Blocks(
|
|
| 1077 |
|
| 1078 |
jobs_table.select(
|
| 1079 |
fn=select_job,
|
| 1080 |
-
outputs=[selected_job_id, result_text]
|
| 1081 |
)
|
| 1082 |
|
| 1083 |
select_job_btn.click(
|
| 1084 |
-
fn=
|
| 1085 |
inputs=[selected_job_id],
|
| 1086 |
-
outputs=[
|
| 1087 |
-
|
| 1088 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1089 |
inputs=[selected_job_id],
|
| 1090 |
-
outputs=[
|
| 1091 |
)
|
| 1092 |
|
| 1093 |
delete_job_btn.click(
|
|
@@ -1145,6 +1302,8 @@ with gr.Blocks(
|
|
| 1145 |
)
|
| 1146 |
|
| 1147 |
|
|
|
|
|
|
|
| 1148 |
if __name__ == "__main__":
|
| 1149 |
import os
|
| 1150 |
app.queue().launch(
|
|
|
|
| 3 |
import asyncio
|
| 4 |
import uuid
|
| 5 |
import threading
|
| 6 |
+
import subprocess
|
| 7 |
+
import shutil
|
| 8 |
from datetime import datetime
|
| 9 |
import logging
|
| 10 |
import traceback
|
|
|
|
| 73 |
try:
|
| 74 |
# Remove the entire output directory for this job
|
| 75 |
output_dir = os.path.dirname(job['output_file'])
|
|
|
|
| 76 |
shutil.rmtree(output_dir, ignore_errors=True)
|
| 77 |
except Exception as e:
|
| 78 |
logger.error(f"Error removing output files: {e}")
|
|
|
|
| 723 |
)
|
| 724 |
|
| 725 |
with gr.Tab("π Job History & Management"):
|
| 726 |
+
# Job list table (full width)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
jobs_table = gr.Dataframe(
|
| 728 |
headers=["ID", "Topic", "Status", "Progress (%)", "Start Time", "Message"],
|
| 729 |
datatype=["str", "str", "str", "number", "str", "str"],
|
| 730 |
interactive=False,
|
| 731 |
+
label=None,
|
| 732 |
+
wrap=True,
|
| 733 |
+
elem_classes=["job-history-table"]
|
| 734 |
)
|
| 735 |
+
# Action buttons (horizontal row, full width)
|
| 736 |
with gr.Row():
|
| 737 |
+
select_job_btn = gr.Button("ποΈ View Details", variant="primary", size="sm")
|
| 738 |
+
delete_job_btn = gr.Button("ποΈ Delete", variant="stop", size="sm")
|
| 739 |
+
download_job_btn = gr.Button("πΎ Download", variant="secondary", size="sm")
|
| 740 |
+
refresh_jobs_btn = gr.Button("π Refresh List", variant="secondary", size="sm")
|
| 741 |
+
clear_completed_btn = gr.Button("π§Ή Clear Completed", variant="secondary", size="sm")
|
| 742 |
+
clear_all_btn = gr.Button("ποΈ Clear All", variant="stop", size="sm")
|
| 743 |
+
selected_job_id = gr.Textbox(label="Selected Job ID", visible=False)
|
| 744 |
+
# Job details viewer (full width, below buttons)
|
| 745 |
+
with gr.Group(elem_classes=["job-details-panel"]):
|
| 746 |
+
gr.Markdown("""
|
| 747 |
+
<div style='font-size:1.2em; font-weight:600; margin-bottom:0.5em;'>
|
| 748 |
+
ποΈ <span style='color:#3b82f6'>Job Details Viewer</span>
|
| 749 |
+
</div>
|
| 750 |
+
""")
|
| 751 |
+
close_details_btn = gr.Button("β¬
οΈ Back to Job List", variant="secondary", size="sm", visible=False)
|
| 752 |
+
job_details_container = gr.Column(visible=False)
|
| 753 |
+
with job_details_container:
|
| 754 |
+
with gr.Row():
|
| 755 |
+
with gr.Column(scale=2):
|
| 756 |
+
job_topic_display = gr.Textbox(label="π Topic", interactive=False)
|
| 757 |
+
job_description_display = gr.Textbox(label="π Description", interactive=False, lines=3)
|
| 758 |
+
job_model_display = gr.Textbox(label="π€ Model Used", interactive=False)
|
| 759 |
+
with gr.Column(scale=1):
|
| 760 |
+
job_status_display = gr.Textbox(label="π Status", interactive=False)
|
| 761 |
+
job_progress_display = gr.Number(label="π Progress (%)", interactive=False)
|
| 762 |
+
job_start_time_display = gr.Textbox(label="β° Start Time", interactive=False)
|
| 763 |
+
with gr.Row():
|
| 764 |
+
job_processing_time_display = gr.Textbox(label="β±οΈ Processing Time", interactive=False)
|
| 765 |
+
job_message_display = gr.Textbox(label="π¬ Current Message", interactive=False)
|
| 766 |
+
with gr.Column(visible=False) as job_video_container:
|
| 767 |
+
gr.Markdown("### π¬ Generated Video")
|
| 768 |
+
job_video_player = gr.Video(
|
| 769 |
+
label="Video Output",
|
| 770 |
+
interactive=False,
|
| 771 |
+
show_download_button=True,
|
| 772 |
+
height=300
|
| 773 |
+
)
|
| 774 |
+
with gr.Row():
|
| 775 |
+
with gr.Column(scale=1):
|
| 776 |
+
job_thumbnail_display = gr.Image(
|
| 777 |
+
label="πΌοΈ Thumbnail",
|
| 778 |
+
height=150,
|
| 779 |
+
interactive=False
|
| 780 |
+
)
|
| 781 |
+
with gr.Column(scale=2):
|
| 782 |
+
job_scene_gallery = gr.Gallery(
|
| 783 |
+
label="π¨ Scene Previews",
|
| 784 |
+
columns=3,
|
| 785 |
+
object_fit="contain",
|
| 786 |
+
height=150,
|
| 787 |
+
show_download_button=True
|
| 788 |
+
)
|
| 789 |
+
with gr.Column(visible=False) as job_error_container:
|
| 790 |
+
gr.Markdown("### β Error Details")
|
| 791 |
+
job_error_display = gr.Textbox(
|
| 792 |
+
label="Error Message",
|
| 793 |
+
interactive=False,
|
| 794 |
+
lines=3
|
| 795 |
+
)
|
| 796 |
+
job_stack_trace_display = gr.Textbox(
|
| 797 |
+
label="Stack Trace",
|
| 798 |
+
interactive=False,
|
| 799 |
+
lines=5,
|
| 800 |
+
max_lines=10
|
| 801 |
+
)
|
| 802 |
+
no_job_selected = gr.Markdown(
|
| 803 |
+
"""
|
| 804 |
+
<div style='padding:2em 0;text-align:center;color:#888;'>
|
| 805 |
+
<b>π No Job Selected</b><br>
|
| 806 |
+
Select a job from the list to view its details.
|
| 807 |
+
</div>
|
| 808 |
+
""",
|
| 809 |
+
visible=True
|
| 810 |
+
)
|
| 811 |
+
|
| 812 |
with gr.Tab("βΉοΈ Help & Documentation"):
|
| 813 |
gr.Markdown("""
|
| 814 |
## π― How to Use Theory2Manim
|
|
|
|
| 1091 |
)
|
| 1092 |
|
| 1093 |
# Job history tab functions
|
| 1094 |
+
|
| 1095 |
def load_job_list():
|
| 1096 |
jobs = get_job_list()
|
| 1097 |
rows = []
|
|
|
|
| 1105 |
formatted_time = start_time
|
| 1106 |
else:
|
| 1107 |
formatted_time = 'Unknown'
|
|
|
|
| 1108 |
rows.append([
|
| 1109 |
+
job['id'][:8] + '...',
|
| 1110 |
+
job['topic'][:50] + ('...' if len(job['topic']) > 50 else ''),
|
| 1111 |
+
job['status'].title(),
|
| 1112 |
+
job['progress'],
|
| 1113 |
formatted_time,
|
| 1114 |
job['message'][:100] + ('...' if len(job['message']) > 100 else '')
|
| 1115 |
])
|
| 1116 |
return rows
|
| 1117 |
|
| 1118 |
def select_job(evt: gr.EventData):
|
| 1119 |
+
if not evt or not hasattr(evt, 'index') or not evt.index:
|
| 1120 |
+
# No job selected
|
| 1121 |
+
return "", "No job selected", gr.update(visible=False)
|
| 1122 |
+
selected_row = evt.index[0]
|
| 1123 |
jobs = get_job_list()
|
| 1124 |
if selected_row < len(jobs):
|
| 1125 |
+
# Job selected
|
| 1126 |
+
return jobs[selected_row]['id'], f"Selected job: {jobs[selected_row]['topic']}", gr.update(visible=True)
|
| 1127 |
+
return "", "No job selected", gr.update(visible=False)
|
| 1128 |
|
| 1129 |
+
def back_to_job_list():
|
| 1130 |
+
# Show job list, hide details
|
| 1131 |
+
return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
|
| 1132 |
+
|
| 1133 |
+
def view_job_details(job_id):
|
| 1134 |
+
"""View details of a selected job."""
|
| 1135 |
+
if not job_id or job_id not in job_status:
|
| 1136 |
+
# Return 17 outputs, all hidden or empty
|
| 1137 |
+
return (
|
| 1138 |
+
gr.update(visible=False), # job_details_container
|
| 1139 |
+
gr.update(visible=True), # no_job_selected
|
| 1140 |
+
"", "", "", "", 0, "", "", "", # topic, desc, model, status, progress, start, proc_time, msg
|
| 1141 |
+
gr.update(visible=False), # job_video_container
|
| 1142 |
+
gr.update(visible=False, value=None), # job_video_player
|
| 1143 |
+
gr.update(visible=False, value=None), # job_thumbnail_display
|
| 1144 |
+
gr.update(visible=False, value=[]), # job_scene_gallery
|
| 1145 |
+
gr.update(visible=False), # job_error_container
|
| 1146 |
+
gr.update(visible=False, value=""), # job_error_display
|
| 1147 |
+
gr.update(visible=False, value="") # job_stack_trace_display
|
| 1148 |
+
)
|
| 1149 |
+
job = job_status[job_id]
|
| 1150 |
+
# Format start time
|
| 1151 |
+
start_time = job.get('start_time', '')
|
| 1152 |
+
if start_time:
|
| 1153 |
+
try:
|
| 1154 |
+
dt = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
|
| 1155 |
+
formatted_time = dt.strftime('%Y-%m-%d %H:%M:%S')
|
| 1156 |
+
except:
|
| 1157 |
+
formatted_time = start_time
|
| 1158 |
+
else:
|
| 1159 |
+
formatted_time = 'Unknown'
|
| 1160 |
+
|
| 1161 |
+
# Video and error visibility
|
| 1162 |
+
is_completed = job.get('status') == 'completed'
|
| 1163 |
+
is_failed = job.get('status') == 'failed'
|
| 1164 |
+
# Always return 17 outputs in order
|
| 1165 |
+
return (
|
| 1166 |
+
gr.update(visible=True), # job_details_container
|
| 1167 |
+
gr.update(visible=False), # no_job_selected
|
| 1168 |
+
job.get('topic', ''),
|
| 1169 |
+
job.get('description', ''),
|
| 1170 |
+
job.get('model', ''),
|
| 1171 |
+
gr.update(value=job.get('status', '').title()), # status_display
|
| 1172 |
+
job.get('progress', 0),
|
| 1173 |
+
formatted_time,
|
| 1174 |
+
job.get('processing_time', ''),
|
| 1175 |
+
job.get('message', ''),
|
| 1176 |
+
gr.update(visible=is_completed), # job_video_container
|
| 1177 |
+
gr.update(visible=is_completed, value=job.get('output_file') if is_completed else None), # job_video_player
|
| 1178 |
+
gr.update(visible=is_completed and job.get('thumbnail') is not None, value=job.get('thumbnail') if is_completed else None), # job_thumbnail_display
|
| 1179 |
+
gr.update(visible=is_completed, value=job.get('scene_snapshots', []) if is_completed else []), # job_scene_gallery
|
| 1180 |
+
gr.update(visible=is_failed), # job_error_container
|
| 1181 |
+
gr.update(visible=is_failed, value=job.get('error', '') if is_failed else ""), # job_error_display
|
| 1182 |
+
gr.update(visible=is_failed, value=job.get('stack_trace', '') if is_failed else "") # job_stack_trace_display
|
| 1183 |
+
)
|
| 1184 |
+
|
| 1185 |
def delete_selected_job(job_id):
|
| 1186 |
+
"""Delete the selected job and update the UI."""
|
| 1187 |
+
if not job_id or job_id not in job_status:
|
| 1188 |
+
return "Job not found", None, gr.update(visible=False)
|
| 1189 |
+
|
| 1190 |
+
# Delete the job
|
| 1191 |
+
result = delete_job(job_id)
|
| 1192 |
+
|
| 1193 |
+
# Update job list
|
| 1194 |
+
jobs = get_job_list()
|
| 1195 |
+
|
| 1196 |
+
# Refresh job table
|
| 1197 |
+
return result, gr.update(value=load_job_list()), gr.update(visible=False)
|
| 1198 |
+
|
| 1199 |
+
def download_job_results(job_id):
|
| 1200 |
+
"""Download the results of a job."""
|
| 1201 |
+
if not job_id or job_id not in job_status:
|
| 1202 |
+
return "Job not found", None
|
| 1203 |
+
|
| 1204 |
+
job = job_status[job_id]
|
| 1205 |
+
output_file = job.get('output_file')
|
| 1206 |
+
|
| 1207 |
+
if not output_file or not os.path.exists(output_file):
|
| 1208 |
+
return "Output file not found", None
|
| 1209 |
+
|
| 1210 |
+
return "Download started", output_file
|
| 1211 |
|
| 1212 |
+
# Connect job history tab event handlers
|
| 1213 |
refresh_jobs_btn.click(
|
| 1214 |
fn=load_job_list,
|
| 1215 |
outputs=[jobs_table]
|
|
|
|
| 1220 |
|
| 1221 |
jobs_table.select(
|
| 1222 |
fn=select_job,
|
| 1223 |
+
outputs=[selected_job_id, result_text, close_details_btn]
|
| 1224 |
)
|
| 1225 |
|
| 1226 |
select_job_btn.click(
|
| 1227 |
+
fn=view_job_details,
|
| 1228 |
inputs=[selected_job_id],
|
| 1229 |
+
outputs=[
|
| 1230 |
+
job_details_container, no_job_selected,
|
| 1231 |
+
job_topic_display, job_description_display, job_model_display,
|
| 1232 |
+
job_status_display, job_progress_display, job_start_time_display,
|
| 1233 |
+
job_processing_time_display, job_message_display,
|
| 1234 |
+
job_video_container, job_video_player, job_thumbnail_display, job_scene_gallery,
|
| 1235 |
+
job_error_container, job_error_display, job_stack_trace_display
|
| 1236 |
+
]
|
| 1237 |
+
)
|
| 1238 |
+
|
| 1239 |
+
close_details_btn.click(
|
| 1240 |
+
fn=back_to_job_list,
|
| 1241 |
+
outputs=[job_details_container, no_job_selected, close_details_btn]
|
| 1242 |
+
)
|
| 1243 |
+
|
| 1244 |
+
download_job_btn.click(
|
| 1245 |
+
fn=download_job_results,
|
| 1246 |
inputs=[selected_job_id],
|
| 1247 |
+
outputs=[result_text]
|
| 1248 |
)
|
| 1249 |
|
| 1250 |
delete_job_btn.click(
|
|
|
|
| 1302 |
)
|
| 1303 |
|
| 1304 |
|
| 1305 |
+
|
| 1306 |
+
|
| 1307 |
if __name__ == "__main__":
|
| 1308 |
import os
|
| 1309 |
app.queue().launch(
|