|
import gradio as gr |
|
import random |
|
from datasets import load_dataset |
|
import csv |
|
from datetime import datetime |
|
import os |
|
import pandas as pd |
|
import json |
|
from huggingface_hub import CommitScheduler, HfApi, snapshot_download |
|
import shutil |
|
import uuid |
|
import git |
|
from pathlib import Path |
|
from io import BytesIO |
|
import PIL |
|
import time |
|
import re |
|
|
|
api = HfApi(token=os.environ["HF_TOKEN"]) |
|
|
|
RESULTS_BACKUP_REPO = "taesiri/PhotoEditBattleResults" |
|
MAIN_DATASET_REPO = "taesiri/IERv2-BattlePairs" |
|
|
|
dataset = load_dataset(MAIN_DATASET_REPO, split="train") |
|
dataset_post_ids = list( |
|
set( |
|
load_dataset(MAIN_DATASET_REPO, columns=["post_id"], split="train") |
|
.to_pandas() |
|
.post_id.tolist() |
|
) |
|
) |
|
|
|
|
|
|
|
def sync_with_hub(): |
|
""" |
|
Synchronize local data with the hub by cloning the dataset repo |
|
""" |
|
print("Starting sync with hub...") |
|
data_dir = Path("./data") |
|
local_csv_path = data_dir / "evaluation_results_exp.csv" |
|
|
|
|
|
local_data = None |
|
if local_csv_path.exists(): |
|
local_data = pd.read_csv(local_csv_path) |
|
print(f"Found local data with {len(local_data)} entries") |
|
|
|
|
|
token = os.environ["HF_TOKEN"] |
|
username = "taesiri" |
|
repo_url = ( |
|
f"https://{username}:{token}@huggingface.co/datasets/{RESULTS_BACKUP_REPO}" |
|
) |
|
hub_data_dir = Path("hub_data") |
|
|
|
if hub_data_dir.exists(): |
|
print("Pulling latest changes...") |
|
repo = git.Repo(hub_data_dir) |
|
origin = repo.remotes.origin |
|
if "https://" in origin.url: |
|
origin.set_url(repo_url) |
|
origin.pull() |
|
else: |
|
print("Cloning repository...") |
|
git.Repo.clone_from(repo_url, hub_data_dir) |
|
|
|
|
|
hub_data_source = hub_data_dir / "data" |
|
if hub_data_source.exists(): |
|
data_dir.mkdir(exist_ok=True) |
|
hub_csv_path = hub_data_source / "evaluation_results_exp.csv" |
|
|
|
if hub_csv_path.exists(): |
|
hub_data = pd.read_csv(hub_csv_path) |
|
print(f"Found hub data with {len(hub_data)} entries") |
|
|
|
if local_data is not None: |
|
|
|
merged_data = pd.concat([local_data, hub_data]).drop_duplicates() |
|
print(f"Merged data has {len(merged_data)} entries") |
|
|
|
|
|
merged_data.to_csv(local_csv_path, index=False) |
|
else: |
|
|
|
shutil.copy2(hub_csv_path, local_csv_path) |
|
|
|
|
|
for item in hub_data_source.glob("*"): |
|
if item.is_file() and item.name != "evaluation_results_exp.csv": |
|
shutil.copy2(item, data_dir / item.name) |
|
elif item.is_dir(): |
|
dest = data_dir / item.name |
|
if not dest.exists(): |
|
shutil.copytree(item, dest) |
|
|
|
|
|
if hub_data_dir.exists(): |
|
shutil.rmtree(hub_data_dir) |
|
print("Finished syncing with hub!") |
|
|
|
|
|
scheduler = CommitScheduler( |
|
repo_id=RESULTS_BACKUP_REPO, |
|
repo_type="dataset", |
|
folder_path="./data", |
|
path_in_repo="data", |
|
every=1, |
|
) |
|
|
|
|
|
def save_evaluation( |
|
post_id, model_a, model_b, verdict, username, start_time, end_time, dataset_idx |
|
): |
|
"""Save evaluation results to CSV including timing, username and dataset index information.""" |
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
duration = end_time - start_time |
|
|
|
os.makedirs("data", exist_ok=True) |
|
filename = "data/evaluation_results_exp.csv" |
|
|
|
|
|
if not os.path.exists(filename): |
|
with open(filename, "w", newline="") as f: |
|
writer = csv.writer(f) |
|
writer.writerow( |
|
[ |
|
"timestamp", |
|
"post_id", |
|
"model_a", |
|
"model_b", |
|
"verdict", |
|
"username", |
|
"start_time", |
|
"end_time", |
|
"duration_seconds", |
|
"dataset_idx", |
|
] |
|
) |
|
|
|
|
|
with open(filename, "a", newline="") as f: |
|
writer = csv.writer(f) |
|
writer.writerow( |
|
[ |
|
timestamp, |
|
post_id, |
|
model_a, |
|
model_b, |
|
verdict, |
|
username, |
|
start_time, |
|
end_time, |
|
duration, |
|
dataset_idx, |
|
] |
|
) |
|
|
|
print( |
|
f"Saved evaluation: {post_id} - Model A: {model_a} - Model B: {model_b} - Verdict: {verdict} - Duration: {duration:.2f}s" |
|
) |
|
|
|
|
|
def get_annotated_indices(username): |
|
"""Get list of dataset indices already annotated by this user""" |
|
filename = "data/evaluation_results_exp.csv" |
|
if not os.path.exists(filename): |
|
print(f"No annotations found for user {username} (file doesn't exist)") |
|
return set() |
|
|
|
try: |
|
df = pd.read_csv(filename) |
|
if "dataset_idx" not in df.columns or "username" not in df.columns: |
|
print(f"No annotations found for user {username} (missing columns)") |
|
return set() |
|
user_annotations = df[df["username"] == username]["dataset_idx"].tolist() |
|
print(f"User {username} has already processed {len(user_annotations)} posts") |
|
return set(user_annotations) |
|
except: |
|
print(f"Error reading annotations for user {username}") |
|
return set() |
|
|
|
|
|
def get_annotated_post_ids(username): |
|
"""Get list of post_ids already annotated by this user""" |
|
filename = "data/evaluation_results_exp.csv" |
|
if not os.path.exists(filename): |
|
print(f"No annotations found for user {username} (file doesn't exist)") |
|
return set() |
|
|
|
try: |
|
df = pd.read_csv(filename) |
|
if "post_id" not in df.columns or "username" not in df.columns: |
|
print(f"No annotations found for user {username} (missing columns)") |
|
return set() |
|
user_annotations = df[df["username"] == username]["post_id"].tolist() |
|
print(f"User {username} has seen {len(set(user_annotations))} unique posts") |
|
return set(user_annotations) |
|
except: |
|
print(f"Error reading annotations for user {username}") |
|
return set() |
|
|
|
|
|
def get_random_sample(username): |
|
"""Get a random sample trying to avoid previously seen post_ids""" |
|
|
|
annotated_indices = get_annotated_indices(username) |
|
annotated_post_ids = get_annotated_post_ids(username) |
|
|
|
|
|
all_indices = set(range(len(dataset))) |
|
available_indices = list(all_indices - annotated_indices) |
|
|
|
if not available_indices: |
|
|
|
available_indices = list(all_indices) |
|
|
|
|
|
max_attempts = 5 |
|
for _ in range(max_attempts): |
|
idx = random.choice(available_indices) |
|
sample = dataset[idx] |
|
if sample["post_id"] not in annotated_post_ids: |
|
break |
|
|
|
available_indices.remove(idx) |
|
if not available_indices: |
|
|
|
break |
|
|
|
|
|
if random.choice([True, False]): |
|
|
|
image_a = sample["ai_edited_image"] |
|
image_b = sample["human_edited_image"] |
|
model_a = sample["model"] |
|
model_b = "HUMAN" |
|
else: |
|
|
|
image_a = sample["human_edited_image"] |
|
image_b = sample["ai_edited_image"] |
|
model_a = "HUMAN" |
|
model_b = sample["model"] |
|
|
|
return { |
|
"post_id": sample["post_id"], |
|
"instruction": '<div style="font-size: 1.8em; font-weight: bold; padding: 20px; background-color: white; border-radius: 10px; margin: 10px;"><span style="color: #888888;">Request:</span> <span style="color: black;">' |
|
+ sample["instruction"] |
|
+ "</span></div>", |
|
"simplified_instruction": '<div style="font-size: 1.8em; font-weight: bold; padding: 20px; background-color: white; border-radius: 10px; margin: 10px;"><span style="color: #888888;">Request:</span> <span style="color: black;">' |
|
+ sample["simplified_instruction"] |
|
+ "</span></div>", |
|
"source_image": sample["source_image"], |
|
"image_a": image_a, |
|
"image_b": image_b, |
|
"model_a": model_a, |
|
"model_b": model_b, |
|
"dataset_idx": idx, |
|
} |
|
|
|
|
|
def evaluate(verdict, state): |
|
"""Handle evaluation button clicks with timing""" |
|
if state is None: |
|
return ( |
|
None, |
|
None, |
|
None, |
|
None, |
|
None, |
|
None, |
|
None, |
|
False, |
|
False, |
|
False, |
|
False, |
|
None, |
|
gr.update(variant="secondary"), |
|
gr.update(variant="secondary"), |
|
gr.update(variant="secondary"), |
|
gr.update(variant="secondary"), |
|
None, |
|
None, |
|
"", |
|
) |
|
|
|
|
|
end_time = time.time() |
|
save_evaluation( |
|
state["post_id"], |
|
state["model_a"], |
|
state["model_b"], |
|
verdict, |
|
state["username"], |
|
state["start_time"], |
|
end_time, |
|
state["dataset_idx"], |
|
) |
|
|
|
|
|
next_sample = get_random_sample(state["username"]) |
|
|
|
next_state = next_sample.copy() |
|
next_state["username"] = state["username"] |
|
next_state["start_time"] = time.time() |
|
|
|
|
|
a_better_reset = gr.update(variant="secondary") |
|
b_better_reset = gr.update(variant="secondary") |
|
neither_reset = gr.update(variant="secondary") |
|
tie_reset = gr.update(variant="secondary") |
|
|
|
return ( |
|
next_sample["source_image"], |
|
next_sample["image_a"], |
|
next_sample["image_b"], |
|
next_sample["instruction"], |
|
next_sample["simplified_instruction"], |
|
f"Model A: {next_sample['model_a']} | Model B: {next_sample['model_b']}", |
|
next_state, |
|
None, |
|
False, |
|
False, |
|
False, |
|
False, |
|
a_better_reset, |
|
b_better_reset, |
|
neither_reset, |
|
tie_reset, |
|
next_sample["post_id"], |
|
next_sample["simplified_instruction"], |
|
state["username"], |
|
) |
|
|
|
|
|
def select_verdict(verdict, state): |
|
"""Handle first step selection""" |
|
if state is None: |
|
return None, False, False, False, False |
|
return ( |
|
verdict, |
|
verdict == "A is better", |
|
verdict == "B is better", |
|
verdict == "Neither is good", |
|
verdict == "Tie", |
|
) |
|
|
|
|
|
def is_valid_email(email): |
|
""" |
|
Validate email format and content more strictly: |
|
- Check basic email format |
|
- Prevent common injection attempts |
|
- Limit length |
|
- Restrict to printable ASCII characters |
|
""" |
|
if not email or not isinstance(email, str): |
|
return False |
|
|
|
|
|
if len(email) > 254: |
|
return False |
|
|
|
|
|
email = email.strip() |
|
|
|
|
|
dangerous_chars = [";", '"', "'", ",", "\\", "\n", "\r", "\t"] |
|
if any(char in email for char in dangerous_chars): |
|
return False |
|
|
|
|
|
if not all(32 <= ord(char) <= 126 for char in email): |
|
return False |
|
|
|
|
|
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" |
|
if not re.match(pattern, email): |
|
return False |
|
|
|
|
|
if ".." in email: |
|
return False |
|
if email.count("@") != 1: |
|
return False |
|
|
|
|
|
local, domain = email.split("@") |
|
if len(local) > 64 or len(domain) > 255: |
|
return False |
|
|
|
return True |
|
|
|
|
|
def handle_username_submit(email, current_page): |
|
"""Handle email submission with enhanced validation""" |
|
try: |
|
if not email: |
|
gr.Warning("Please enter an email address") |
|
return current_page, gr.update(value=email), gr.update(value=""), None |
|
|
|
|
|
email = str(email).strip() |
|
|
|
if not is_valid_email(email): |
|
gr.Warning("Please enter a valid email address (e.g., [email protected])") |
|
return current_page, gr.update(value=email), gr.update(value=""), None |
|
|
|
|
|
safe_email = email.replace('"', "").replace("'", "") |
|
|
|
return ( |
|
2, |
|
gr.update(value=""), |
|
gr.update(value=safe_email), |
|
safe_email, |
|
) |
|
|
|
except Exception as e: |
|
print(f"Error in handle_username_submit: {str(e)}") |
|
gr.Warning("An error occurred. Please try again.") |
|
return current_page, gr.update(value=""), gr.update(value=""), None |
|
|
|
|
|
def initialize(username): |
|
"""Initialize the interface with first sample""" |
|
sample = get_random_sample(username) |
|
|
|
state = sample.copy() |
|
state["username"] = username |
|
state["start_time"] = time.time() |
|
|
|
return ( |
|
sample["source_image"], |
|
sample["image_a"], |
|
sample["image_b"], |
|
sample["instruction"], |
|
sample["simplified_instruction"], |
|
f"Model A: {sample['model_a']} | Model B: {sample['model_b']}", |
|
state, |
|
None, |
|
False, |
|
False, |
|
False, |
|
False, |
|
sample["post_id"], |
|
sample["simplified_instruction"], |
|
username or "", |
|
) |
|
|
|
|
|
def update_button_styles(verdict): |
|
"""Update button styles based on selection""" |
|
|
|
a_better_style = gr.update( |
|
value="☝️ A is better" if verdict == "A is better" else "☝️ A is better" |
|
) |
|
b_better_style = gr.update( |
|
value="☝️ B is better" if verdict == "B is better" else "☝️ B is better" |
|
) |
|
neither_style = gr.update( |
|
value="👎 Both are bad" if verdict == "Neither is good" else "👎 Both are bad" |
|
) |
|
tie_style = gr.update(value="🤝 Tie" if verdict == "Tie" else "🤝 Tie") |
|
return a_better_style, b_better_style, neither_style, tie_style |
|
|
|
|
|
|
|
def create_instruction_page(html_content, image_path=None): |
|
"""Helper function to create consistent instruction pages""" |
|
with gr.Column(): |
|
gr.HTML(html_content) |
|
if image_path: |
|
gr.Image(image_path, container=False) |
|
|
|
|
|
def advance_page(current_page): |
|
"""Handle next button clicks to advance pages""" |
|
return current_page + 1 |
|
|
|
|
|
|
|
with gr.Blocks() as demo: |
|
|
|
current_page = gr.State(1) |
|
username_state = gr.State(None) |
|
|
|
|
|
with gr.Column() as page_container: |
|
|
|
with gr.Column(visible=True) as page1: |
|
create_instruction_page( |
|
""" |
|
<div style="text-align: center; padding: 20px;"> |
|
<h1>Welcome to the Image Edit Evaluation</h1> |
|
<p>Help us evaluate different image edits for a given instruction.</p> |
|
</div> |
|
""", |
|
image_path="./instructions/home.jpg", |
|
) |
|
username_input = gr.Textbox( |
|
label="Please enter your email address (if you don't want to share your email, please enter a fake email)", |
|
placeholder="[email protected]", |
|
) |
|
start_btn = gr.Button("Start", variant="primary") |
|
|
|
|
|
with gr.Column(visible=False) as page2: |
|
create_instruction_page( |
|
""" |
|
<div style="text-align: center; padding: 20px;"> |
|
<h1>How to Evaluate Edits</h1> |
|
</div> |
|
""", |
|
image_path="./instructions/page2.jpg", |
|
) |
|
next_btn1 = gr.Button( |
|
"Start Evaluation", variant="primary" |
|
) |
|
|
|
|
|
with gr.Column(visible=False) as main_ui: |
|
|
|
gr.HTML( |
|
""" |
|
<div style="padding: 0.8rem; margin-bottom: 0.8rem; border-radius: 0.5rem; color: white; text-align: center;"> |
|
<div style="font-size: 1.2rem; margin-bottom: 0.5rem;">Read the user instruction, look at the source image, then evaluate which edit (A or B) best satisfies the request better.</div> |
|
<div style="font-size: 1rem;"> |
|
<strong>🤝 Tie</strong> | |
|
<strong> A is better</strong> | |
|
<strong> B is better</strong> |
|
</div> |
|
<div style="color: #ff4444; font-size: 0.9rem; margin-top: 0.5rem;"> |
|
Please ignore any watermark on the image. Your rating should not be affected by any watermark on the image. |
|
</div> |
|
</div> |
|
""" |
|
) |
|
|
|
with gr.Row(): |
|
simplified_instruction = gr.Textbox( |
|
label="Simplified Instruction", show_label=True, visible=False |
|
) |
|
instruction = gr.HTML(label="Original Instruction", show_label=True) |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
source_image = gr.Image( |
|
label="Source Image", show_label=True, height=500 |
|
) |
|
gr.HTML("<h2 style='text-align: center;'>Source Image</h2>") |
|
tie_btn = gr.Button("🤝 Tie", variant="secondary") |
|
with gr.Column(): |
|
image_a = gr.Image(label="Image A", show_label=True, height=500) |
|
gr.HTML("<h2 style='text-align: center;'>Image A</h2>") |
|
a_better_btn = gr.Button("☝️ A is better", variant="secondary") |
|
with gr.Column(): |
|
image_b = gr.Image(label="Image B", show_label=True, height=500) |
|
gr.HTML("<h2 style='text-align: center;'>Image B</h2>") |
|
b_better_btn = gr.Button("☝️ B is better", variant="secondary") |
|
|
|
|
|
with gr.Row(): |
|
confirm_btn = gr.Button( |
|
"Confirm Selection", variant="primary", visible=False |
|
) |
|
with gr.Row(): |
|
neither_btn = gr.Button( |
|
"👎 Both are bad", variant="secondary", visible=False |
|
) |
|
|
|
with gr.Accordion("DEBUG", open=False, visible=False): |
|
with gr.Column(): |
|
post_id_display = gr.Textbox( |
|
label="Post ID", show_label=True, interactive=False |
|
) |
|
model_info = gr.Textbox(label="Model Information", show_label=True) |
|
simplified_instruction_debug = gr.Textbox( |
|
label="Simplified Instruction", |
|
show_label=True, |
|
interactive=False, |
|
) |
|
username_debug = gr.Textbox( |
|
label="Username", show_label=True, interactive=False |
|
) |
|
state = gr.State() |
|
selected_verdict = gr.State() |
|
|
|
|
|
a_better_selected = gr.Checkbox(visible=False) |
|
b_better_selected = gr.Checkbox(visible=False) |
|
neither_selected = gr.Checkbox(visible=False) |
|
tie_selected = gr.Checkbox(visible=False) |
|
|
|
def update_confirm_visibility(a_better, b_better, neither, tie): |
|
|
|
if a_better: |
|
return gr.update(visible=True, value="Confirm A is better") |
|
elif b_better: |
|
return gr.update(visible=True, value="Confirm B is better") |
|
elif neither: |
|
return gr.update(visible=True, value="Confirm Neither is good") |
|
elif tie: |
|
return gr.update(visible=True, value="Confirm Tie") |
|
return gr.update(visible=False) |
|
|
|
|
|
demo.load( |
|
lambda: initialize(None), |
|
outputs=[ |
|
source_image, |
|
image_a, |
|
image_b, |
|
instruction, |
|
simplified_instruction, |
|
model_info, |
|
state, |
|
selected_verdict, |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
post_id_display, |
|
simplified_instruction_debug, |
|
username_debug, |
|
], |
|
) |
|
|
|
|
|
a_better_btn.click( |
|
lambda state: select_verdict("A is better", state), |
|
inputs=[state], |
|
outputs=[ |
|
selected_verdict, |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
], |
|
).then( |
|
update_button_styles, |
|
inputs=[selected_verdict], |
|
outputs=[a_better_btn, b_better_btn, neither_btn, tie_btn], |
|
) |
|
|
|
b_better_btn.click( |
|
lambda state: select_verdict("B is better", state), |
|
inputs=[state], |
|
outputs=[ |
|
selected_verdict, |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
], |
|
).then( |
|
update_button_styles, |
|
inputs=[selected_verdict], |
|
outputs=[a_better_btn, b_better_btn, neither_btn, tie_btn], |
|
) |
|
|
|
neither_btn.click( |
|
lambda state: select_verdict("Neither is good", state), |
|
inputs=[state], |
|
outputs=[ |
|
selected_verdict, |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
], |
|
).then( |
|
update_button_styles, |
|
inputs=[selected_verdict], |
|
outputs=[a_better_btn, b_better_btn, neither_btn, tie_btn], |
|
) |
|
|
|
tie_btn.click( |
|
lambda state: select_verdict("Tie", state), |
|
inputs=[state], |
|
outputs=[ |
|
selected_verdict, |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
], |
|
).then( |
|
update_button_styles, |
|
inputs=[selected_verdict], |
|
outputs=[a_better_btn, b_better_btn, neither_btn, tie_btn], |
|
) |
|
|
|
|
|
for checkbox in [ |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
]: |
|
checkbox.change( |
|
update_confirm_visibility, |
|
inputs=[ |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
], |
|
outputs=[confirm_btn], |
|
) |
|
|
|
|
|
confirm_btn.click( |
|
lambda verdict, state: evaluate(verdict, state), |
|
inputs=[selected_verdict, state], |
|
outputs=[ |
|
source_image, |
|
image_a, |
|
image_b, |
|
instruction, |
|
simplified_instruction, |
|
model_info, |
|
state, |
|
selected_verdict, |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
a_better_btn, |
|
b_better_btn, |
|
neither_btn, |
|
tie_btn, |
|
post_id_display, |
|
simplified_instruction_debug, |
|
username_debug, |
|
], |
|
) |
|
|
|
|
|
def update_page_visibility(page_num): |
|
"""Return visibility updates for each page column""" |
|
return [ |
|
gr.update(visible=(page_num == 1)), |
|
gr.update(visible=(page_num == 2)), |
|
gr.update(visible=(page_num == 3)), |
|
] |
|
|
|
|
|
start_btn.click( |
|
handle_username_submit, |
|
inputs=[username_input, current_page], |
|
outputs=[ |
|
current_page, |
|
username_input, |
|
username_debug, |
|
username_state, |
|
], |
|
).then( |
|
update_page_visibility, |
|
inputs=[current_page], |
|
outputs=[page1, page2, main_ui], |
|
).then( |
|
initialize, |
|
inputs=[username_state], |
|
outputs=[ |
|
source_image, |
|
image_a, |
|
image_b, |
|
instruction, |
|
simplified_instruction, |
|
model_info, |
|
state, |
|
selected_verdict, |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
post_id_display, |
|
simplified_instruction_debug, |
|
username_debug, |
|
], |
|
) |
|
|
|
next_btn1.click( |
|
lambda x: 3, |
|
inputs=[current_page], |
|
outputs=current_page, |
|
).then( |
|
update_page_visibility, |
|
inputs=[current_page], |
|
outputs=[page1, page2, main_ui], |
|
).then( |
|
initialize, |
|
inputs=[username_state], |
|
outputs=[ |
|
source_image, |
|
image_a, |
|
image_b, |
|
instruction, |
|
simplified_instruction, |
|
model_info, |
|
state, |
|
selected_verdict, |
|
a_better_selected, |
|
b_better_selected, |
|
neither_selected, |
|
tie_selected, |
|
post_id_display, |
|
simplified_instruction_debug, |
|
username_debug, |
|
], |
|
) |
|
|
|
if __name__ == "__main__": |
|
|
|
sync_with_hub() |
|
demo.launch() |
|
|