Spaces:
Running
Running
import gradio as gr | |
import requests | |
import base64 | |
from PIL import Image | |
import io | |
import logging | |
from typing import List, Dict, Optional, Tuple | |
# Configure logging | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
MAX_PAIRS = 20 | |
SERVER_URL = "https://c73b-163-1-222-182.ngrok-free.app" | |
def create_qa_boxes(num_pairs: int) -> Tuple[List[gr.Textbox], List[gr.Textbox]]: | |
"""Dynamically create question and answer textboxes""" | |
questions = [] | |
answers = [] | |
for i in range(num_pairs): | |
questions.append(gr.Textbox( | |
lines=2, | |
label=f"Question {i+1}", | |
placeholder="Enter question here...", | |
visible=True | |
)) | |
answers.append(gr.Textbox( | |
lines=2, | |
label=f"Answer {i+1}", | |
placeholder="Enter answer here...", | |
visible=True | |
)) | |
return questions, answers | |
def validate_password(password: str) -> Tuple[bool, str, Optional[str]]: | |
"""Validate the password with the server and return the API key if correct""" | |
try: | |
response = requests.post(f"{SERVER_URL}/validate", json={"password": password}) | |
if response.status_code == 200: | |
data = response.json() | |
if data.get("valid"): | |
return True, "Password correct. Loading data...", data.get("api_key") | |
return False, "Incorrect password", None | |
except Exception as e: | |
print(f"Error validating password: {e}") | |
return False, "Server error during validation", None | |
def base64_to_image(base64_str: str) -> Image.Image: | |
"""Convert base64 string to PIL Image""" | |
image_bytes = base64.b64decode(base64_str) | |
return Image.open(io.BytesIO(image_bytes)) | |
def get_next_item(api_key: str) -> Optional[Dict]: | |
"""Get next item from the server""" | |
try: | |
headers = {"X-API-Key": api_key} if api_key else {} | |
response = requests.get(f"{SERVER_URL}/get_data", headers=headers) | |
if response.status_code == 200: | |
return response.json() | |
return None | |
except Exception as e: | |
print(f"Error getting next item: {e}") | |
return None | |
def save_item(data: Dict, api_key: str) -> bool: | |
"""Save item to the server""" | |
try: | |
# Remove image_base64 if present | |
data_to_save = {k: v for k, v in data.items() if k != "image_base64"} | |
headers = {"X-API-Key": api_key} if api_key else {} | |
response = requests.post(f"{SERVER_URL}/save_data", json=data_to_save, headers=headers) | |
return response.status_code == 200 | |
except Exception as e: | |
print(f"Error saving item: {e}") | |
return False | |
def update_interface(data: Optional[Dict]) -> Tuple[Optional[Image.Image], str, List[str], List[str], List[bool], List[bool]]: | |
"""Update interface with new data""" | |
if data is None: | |
logging.warning("update_interface: No data provided") | |
return None, "No data available", [], [], [], [] | |
logging.info(f"update_interface: Received data with {len(data['questions'])} questions") | |
# Convert base64 to image | |
image = base64_to_image(data.get("image_base64")) if data.get("image_base64") else None | |
# Extract questions and answers | |
questions = [qa["question"] for qa in data["questions"]] | |
answers = [qa["answer"] for qa in data["questions"]] | |
updates = [] | |
# First add all question updates | |
for i in range(MAX_PAIRS): | |
if i < len(questions): | |
logging.info(f"Making question {i+1} visible with: {questions[i]}") | |
updates.append(gr.update(value=questions[i], visible=True)) | |
updates.append(gr.update(value=answers[i], visible=True)) | |
else: | |
updates.append(gr.update(value="", visible=False)) | |
updates.append(gr.update(value="", visible=False)) | |
return image, "Data loaded successfully", updates | |
def login(password: str, state: Dict): | |
"""Handle login and load first item""" | |
logging.info("Attempting login") | |
success, message, api_key = validate_password(password) | |
if not success: | |
logging.warning(f"Login failed: {message}") | |
# Return empty updates for both questions and answers | |
empty_updates = [gr.update(value="", visible=False)] * MAX_PAIRS * 2 | |
return [None, message, gr.update(visible=True), gr.update(visible=False), {"api_key": None, "current_data": None}] + empty_updates | |
logging.info("Login successful, fetching first item") | |
# Get first item | |
current_data = get_next_item(api_key) | |
if current_data is None: | |
logging.warning("No items available after login") | |
empty_updates = [gr.update(value="", visible=False)] * MAX_PAIRS * 2 | |
return [None, "No items available", gr.update(visible=True), gr.update(visible=False), {"api_key": api_key, "current_data": None}] + empty_updates | |
# Update interface with new data | |
image, status, question_answers = update_interface(current_data) | |
logging.info("Returning login updates") | |
return [image, status, gr.update(visible=False), gr.update(visible=True), {"api_key": api_key, "current_data": current_data}] + question_answers | |
def save_and_next(*args): | |
"""Save current item and load next one""" | |
# Extract state from the last argument | |
qa_inputs = args[:-1] | |
state = args[-1] | |
current_data = state.get("current_data") | |
api_key = state.get("api_key") | |
logging.info("save_and_next called") | |
if current_data is not None and api_key is not None: | |
# Split inputs into questions and answers | |
questions = qa_inputs[::2] | |
answers = qa_inputs[1::2] | |
# Filter out hidden inputs | |
valid_qa_pairs = [] | |
for q, a in zip(questions, answers): | |
if q is not None and q.strip() and a is not None and a.strip(): # If question exists | |
valid_qa_pairs.append({"question": q, "answer": a}) | |
current_data["questions"] = valid_qa_pairs | |
if not save_item(current_data, api_key): | |
logging.error("Failed to save current item") | |
return [None, "Failed to save item", {"api_key": api_key, "current_data": None}] + [gr.update(value="", visible=False)] * MAX_PAIRS * 2 | |
logging.info("Successfully saved current item, fetching next") | |
# Get next item | |
next_data = get_next_item(api_key) | |
if next_data is None: | |
logging.warning("No more items available after save") | |
empty_updates = [gr.update(value="", visible=False)] * MAX_PAIRS * 2 | |
return [None, "No more items available", {"api_key": api_key, "current_data": None}] + empty_updates | |
image, status, updates = update_interface(next_data) | |
return [image, status, {"api_key": api_key, "current_data": next_data}] + updates | |
# Create the Gradio interface | |
with gr.Blocks() as app: | |
# Initialize session state | |
state = gr.State(value={"api_key": None, "current_data": None}) | |
logging.info("Creating Gradio interface") | |
# Login section | |
with gr.Row(visible=True) as login_row: | |
password_input = gr.Textbox(type="password", label="Password") | |
login_button = gr.Button("Login") | |
login_message = gr.Textbox(label="Status", interactive=False) | |
# Main interface (hidden initially) | |
with gr.Row(visible=False) as main_interface: | |
# Left column - Image | |
with gr.Column(scale=1): | |
image_output = gr.Image(type="pil", label="Image") | |
# Right column - Q&A pairs and controls | |
with gr.Column(scale=1): | |
# Create maximum number of Q&A pairs (they'll be hidden/shown as needed) | |
questions_list = [] | |
answers_list = [] | |
# Create containers for Q&A pairs | |
# Create interleaved Q&A pairs | |
for i in range(MAX_PAIRS): | |
with gr.Group(): | |
# Question box | |
questions_list.append(gr.Textbox( | |
lines=2, | |
label=f"Question {i+1}", | |
placeholder="Enter question here...", | |
visible=False | |
)) | |
# Answer box immediately after its question | |
answers_list.append(gr.Textbox( | |
lines=2, | |
label=f"Answer {i+1}", | |
placeholder="Enter answer here...", | |
visible=False | |
)) | |
save_button = gr.Button("Save and Load Next") | |
status_message = gr.Textbox(label="Status", interactive=False) | |
logging.info("Setting up event handlers") | |
# Set up event handlers | |
all_components = [] | |
for i in range(MAX_PAIRS): | |
all_components.extend([questions_list[i], answers_list[i]]) | |
login_button.click( | |
login, | |
inputs=[password_input, state], | |
outputs=[image_output, login_message, login_row, main_interface, state] + all_components | |
) | |
save_button.click( | |
save_and_next, | |
inputs=all_components + [state], | |
outputs=[image_output, status_message, state] + all_components | |
) | |
if __name__ == "__main__": | |
logging.info("Starting Gradio app") | |
app.launch() | |