Spaces:
Running
Running
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import requests
|
3 |
+
import base64
|
4 |
+
from PIL import Image
|
5 |
+
import io
|
6 |
+
import logging
|
7 |
+
from typing import List, Dict, Optional, Tuple
|
8 |
+
|
9 |
+
# Configure logging
|
10 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
11 |
+
|
12 |
+
MAX_PAIRS = 20
|
13 |
+
SERVER_URL = "https://c73b-163-1-222-182.ngrok-free.app"
|
14 |
+
|
15 |
+
def create_qa_boxes(num_pairs: int) -> Tuple[List[gr.Textbox], List[gr.Textbox]]:
|
16 |
+
"""Dynamically create question and answer textboxes"""
|
17 |
+
questions = []
|
18 |
+
answers = []
|
19 |
+
for i in range(num_pairs):
|
20 |
+
questions.append(gr.Textbox(
|
21 |
+
lines=2,
|
22 |
+
label=f"Question {i+1}",
|
23 |
+
placeholder="Enter question here...",
|
24 |
+
visible=True
|
25 |
+
))
|
26 |
+
answers.append(gr.Textbox(
|
27 |
+
lines=2,
|
28 |
+
label=f"Answer {i+1}",
|
29 |
+
placeholder="Enter answer here...",
|
30 |
+
visible=True
|
31 |
+
))
|
32 |
+
return questions, answers
|
33 |
+
|
34 |
+
def validate_password(password: str) -> Tuple[bool, str, Optional[str]]:
|
35 |
+
"""Validate the password with the server and return the API key if correct"""
|
36 |
+
try:
|
37 |
+
response = requests.post(f"{SERVER_URL}/validate", json={"password": password})
|
38 |
+
if response.status_code == 200:
|
39 |
+
data = response.json()
|
40 |
+
if data.get("valid"):
|
41 |
+
return True, "Password correct. Loading data...", data.get("api_key")
|
42 |
+
return False, "Incorrect password", None
|
43 |
+
except Exception as e:
|
44 |
+
print(f"Error validating password: {e}")
|
45 |
+
return False, "Server error during validation", None
|
46 |
+
|
47 |
+
def base64_to_image(base64_str: str) -> Image.Image:
|
48 |
+
"""Convert base64 string to PIL Image"""
|
49 |
+
image_bytes = base64.b64decode(base64_str)
|
50 |
+
return Image.open(io.BytesIO(image_bytes))
|
51 |
+
|
52 |
+
def get_next_item(api_key: str) -> Optional[Dict]:
|
53 |
+
"""Get next item from the server"""
|
54 |
+
try:
|
55 |
+
headers = {"X-API-Key": api_key} if api_key else {}
|
56 |
+
response = requests.get(f"{SERVER_URL}/get_data", headers=headers)
|
57 |
+
if response.status_code == 200:
|
58 |
+
return response.json()
|
59 |
+
return None
|
60 |
+
except Exception as e:
|
61 |
+
print(f"Error getting next item: {e}")
|
62 |
+
return None
|
63 |
+
|
64 |
+
def save_item(data: Dict, api_key: str) -> bool:
|
65 |
+
"""Save item to the server"""
|
66 |
+
try:
|
67 |
+
# Remove image_base64 if present
|
68 |
+
data_to_save = {k: v for k, v in data.items() if k != "image_base64"}
|
69 |
+
headers = {"X-API-Key": api_key} if api_key else {}
|
70 |
+
response = requests.post(f"{SERVER_URL}/save_data", json=data_to_save, headers=headers)
|
71 |
+
return response.status_code == 200
|
72 |
+
except Exception as e:
|
73 |
+
print(f"Error saving item: {e}")
|
74 |
+
return False
|
75 |
+
|
76 |
+
def update_interface(data: Optional[Dict]) -> Tuple[Optional[Image.Image], str, List[str], List[str], List[bool], List[bool]]:
|
77 |
+
"""Update interface with new data"""
|
78 |
+
if data is None:
|
79 |
+
logging.warning("update_interface: No data provided")
|
80 |
+
return None, "No data available", [], [], [], []
|
81 |
+
|
82 |
+
logging.info(f"update_interface: Received data with {len(data['questions'])} questions")
|
83 |
+
|
84 |
+
# Convert base64 to image
|
85 |
+
image = base64_to_image(data.get("image_base64")) if data.get("image_base64") else None
|
86 |
+
|
87 |
+
# Extract questions and answers
|
88 |
+
questions = [qa["question"] for qa in data["questions"]]
|
89 |
+
answers = [qa["answer"] for qa in data["questions"]]
|
90 |
+
|
91 |
+
updates = []
|
92 |
+
|
93 |
+
# First add all question updates
|
94 |
+
for i in range(MAX_PAIRS):
|
95 |
+
if i < len(questions):
|
96 |
+
logging.info(f"Making question {i+1} visible with: {questions[i]}")
|
97 |
+
updates.append(gr.update(value=questions[i], visible=True))
|
98 |
+
updates.append(gr.update(value=answers[i], visible=True))
|
99 |
+
else:
|
100 |
+
updates.append(gr.update(value="", visible=False))
|
101 |
+
updates.append(gr.update(value="", visible=False))
|
102 |
+
|
103 |
+
return image, "Data loaded successfully", updates
|
104 |
+
|
105 |
+
def login(password: str, state: Dict):
|
106 |
+
"""Handle login and load first item"""
|
107 |
+
logging.info("Attempting login")
|
108 |
+
success, message, api_key = validate_password(password)
|
109 |
+
if not success:
|
110 |
+
logging.warning(f"Login failed: {message}")
|
111 |
+
# Return empty updates for both questions and answers
|
112 |
+
empty_updates = [gr.update(value="", visible=False)] * MAX_PAIRS * 2
|
113 |
+
return [None, message, gr.update(visible=True), gr.update(visible=False), {"api_key": None, "current_data": None}] + empty_updates
|
114 |
+
|
115 |
+
logging.info("Login successful, fetching first item")
|
116 |
+
# Get first item
|
117 |
+
current_data = get_next_item(api_key)
|
118 |
+
if current_data is None:
|
119 |
+
logging.warning("No items available after login")
|
120 |
+
empty_updates = [gr.update(value="", visible=False)] * MAX_PAIRS * 2
|
121 |
+
return [None, "No items available", gr.update(visible=True), gr.update(visible=False), {"api_key": api_key, "current_data": None}] + empty_updates
|
122 |
+
|
123 |
+
# Update interface with new data
|
124 |
+
image, status, question_answers = update_interface(current_data)
|
125 |
+
|
126 |
+
logging.info("Returning login updates")
|
127 |
+
return [image, status, gr.update(visible=False), gr.update(visible=True), {"api_key": api_key, "current_data": current_data}] + question_answers
|
128 |
+
|
129 |
+
def save_and_next(*args):
|
130 |
+
"""Save current item and load next one"""
|
131 |
+
# Extract state from the last argument
|
132 |
+
qa_inputs = args[:-1]
|
133 |
+
state = args[-1]
|
134 |
+
current_data = state.get("current_data")
|
135 |
+
api_key = state.get("api_key")
|
136 |
+
|
137 |
+
logging.info("save_and_next called")
|
138 |
+
|
139 |
+
if current_data is not None and api_key is not None:
|
140 |
+
# Split inputs into questions and answers
|
141 |
+
questions = qa_inputs[::2]
|
142 |
+
answers = qa_inputs[1::2]
|
143 |
+
|
144 |
+
# Filter out hidden inputs
|
145 |
+
valid_qa_pairs = []
|
146 |
+
for q, a in zip(questions, answers):
|
147 |
+
if q is not None and q.strip() and a is not None and a.strip(): # If question exists
|
148 |
+
valid_qa_pairs.append({"question": q, "answer": a})
|
149 |
+
current_data["questions"] = valid_qa_pairs
|
150 |
+
|
151 |
+
if not save_item(current_data, api_key):
|
152 |
+
logging.error("Failed to save current item")
|
153 |
+
return [None, "Failed to save item", {"api_key": api_key, "current_data": None}] + [gr.update(value="", visible=False)] * MAX_PAIRS * 2
|
154 |
+
|
155 |
+
logging.info("Successfully saved current item, fetching next")
|
156 |
+
|
157 |
+
# Get next item
|
158 |
+
next_data = get_next_item(api_key)
|
159 |
+
if next_data is None:
|
160 |
+
logging.warning("No more items available after save")
|
161 |
+
empty_updates = [gr.update(value="", visible=False)] * MAX_PAIRS * 2
|
162 |
+
return [None, "No more items available", {"api_key": api_key, "current_data": None}] + empty_updates
|
163 |
+
|
164 |
+
image, status, updates = update_interface(next_data)
|
165 |
+
return [image, status, {"api_key": api_key, "current_data": next_data}] + updates
|
166 |
+
|
167 |
+
# Create the Gradio interface
|
168 |
+
with gr.Blocks() as app:
|
169 |
+
# Initialize session state
|
170 |
+
state = gr.State(value={"api_key": None, "current_data": None})
|
171 |
+
|
172 |
+
logging.info("Creating Gradio interface")
|
173 |
+
# Login section
|
174 |
+
with gr.Row(visible=True) as login_row:
|
175 |
+
password_input = gr.Textbox(type="password", label="Password")
|
176 |
+
login_button = gr.Button("Login")
|
177 |
+
login_message = gr.Textbox(label="Status", interactive=False)
|
178 |
+
|
179 |
+
# Main interface (hidden initially)
|
180 |
+
with gr.Row(visible=False) as main_interface:
|
181 |
+
# Left column - Image
|
182 |
+
with gr.Column(scale=1):
|
183 |
+
image_output = gr.Image(type="pil", label="Image")
|
184 |
+
|
185 |
+
# Right column - Q&A pairs and controls
|
186 |
+
with gr.Column(scale=1):
|
187 |
+
# Create maximum number of Q&A pairs (they'll be hidden/shown as needed)
|
188 |
+
questions_list = []
|
189 |
+
answers_list = []
|
190 |
+
|
191 |
+
# Create containers for Q&A pairs
|
192 |
+
|
193 |
+
# Create interleaved Q&A pairs
|
194 |
+
for i in range(MAX_PAIRS):
|
195 |
+
with gr.Group():
|
196 |
+
# Question box
|
197 |
+
questions_list.append(gr.Textbox(
|
198 |
+
lines=2,
|
199 |
+
label=f"Question {i+1}",
|
200 |
+
placeholder="Enter question here...",
|
201 |
+
visible=False
|
202 |
+
))
|
203 |
+
# Answer box immediately after its question
|
204 |
+
answers_list.append(gr.Textbox(
|
205 |
+
lines=2,
|
206 |
+
label=f"Answer {i+1}",
|
207 |
+
placeholder="Enter answer here...",
|
208 |
+
visible=False
|
209 |
+
))
|
210 |
+
|
211 |
+
save_button = gr.Button("Save and Load Next")
|
212 |
+
status_message = gr.Textbox(label="Status", interactive=False)
|
213 |
+
|
214 |
+
logging.info("Setting up event handlers")
|
215 |
+
# Set up event handlers
|
216 |
+
all_components = []
|
217 |
+
for i in range(MAX_PAIRS):
|
218 |
+
all_components.extend([questions_list[i], answers_list[i]])
|
219 |
+
|
220 |
+
login_button.click(
|
221 |
+
login,
|
222 |
+
inputs=[password_input, state],
|
223 |
+
outputs=[image_output, login_message, login_row, main_interface, state] + all_components
|
224 |
+
)
|
225 |
+
|
226 |
+
save_button.click(
|
227 |
+
save_and_next,
|
228 |
+
inputs=all_components + [state],
|
229 |
+
outputs=[image_output, status_message, state] + all_components
|
230 |
+
)
|
231 |
+
|
232 |
+
if __name__ == "__main__":
|
233 |
+
logging.info("Starting Gradio app")
|
234 |
+
app.launch()
|