File size: 9,316 Bytes
e2df94c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
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()