broadfield-dev commited on
Commit
ecded06
Β·
verified Β·
1 Parent(s): 656929a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -80
app.py CHANGED
@@ -2,63 +2,85 @@ import gradio as gr
2
  import json
3
  import logging
4
  import tempfile
5
- import core # Import our new core module
6
 
7
  # --- Configure Logging ---
8
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
9
  logger = logging.getLogger(__name__)
10
 
11
  # ==============================================================================
12
- # UI HELPER FUNCTIONS
13
  # ==============================================================================
14
 
15
- def update_service_dropdown(json_config_string: str):
16
- """Parses the JSON config and updates the dropdown choices."""
17
- if not json_config_string.strip():
18
- return gr.Dropdown(choices=[], value=None, label="Decoder Service (Paste JSON config)"), "Status: Waiting for JSON configuration."
19
 
 
 
 
20
  try:
21
- endpoints = json.loads(json_config_string)
22
- if not isinstance(endpoints, list):
23
- raise TypeError("JSON root must be a list of objects.")
24
-
25
- service_names = []
26
- for service in endpoints:
27
- # Validate that each service has the required keys
 
28
  if not all(k in service for k in ("name", "link", "public_key")):
29
  raise ValueError("Each service object must contain 'name', 'link', and 'public_key' keys.")
30
- service_names.append(service["name"])
 
 
 
 
31
 
32
- # If valid, update the dropdown with the new choices
33
- if not service_names:
34
- return gr.Dropdown(choices=[], value=None, label="Decoder Service (No services found)"), "Status: JSON parsed, but no services were found."
35
-
36
- logger.info(f"Updated dropdown with services: {service_names}")
37
- return gr.Dropdown(choices=service_names, value=service_names[0], label="Select Decoder Service"), f"Status: Loaded {len(service_names)} services. '{service_names[0]}' selected."
38
- except (json.JSONDecodeError, TypeError, ValueError) as e:
39
- logger.error(f"JSON parsing error: {e}")
40
- return gr.Dropdown(choices=[], value=None, label="Decoder Service (Invalid JSON)"), f"Status: Error parsing JSON - {e}"
41
-
42
- def generate_image(json_config_string: str, selected_service_name: str, secret_data_str: str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  """
44
  The main function called by the 'Create' button.
45
  It finds the selected service's public key and calls the core encryption logic.
46
  """
47
- if not all([json_config_string, selected_service_name, secret_data_str]):
48
- raise gr.Error("Configuration, a selected service, and secret data are all required.")
49
 
50
  try:
51
- endpoints = json.loads(json_config_string)
52
-
53
- # Find the public key for the selected service
54
  public_key = None
55
- for service in endpoints:
56
  if service.get("name") == selected_service_name:
57
  public_key = service.get("public_key")
58
  break
59
 
60
  if not public_key:
61
- raise gr.Error(f"Could not find the service '{selected_service_name}' in the provided JSON.")
62
 
63
  # Call the core function to do the heavy lifting
64
  encrypted_image = core.create_encrypted_image(secret_data_str, public_key)
@@ -71,74 +93,76 @@ def generate_image(json_config_string: str, selected_service_name: str, secret_d
71
 
72
  except Exception as e:
73
  logger.error(f"Image creation failed: {e}", exc_info=True)
74
- # Return empty outputs on failure
75
  return None, None, f"❌ Error: {e}"
76
 
77
  # ==============================================================================
78
  # GRADIO INTERFACE
79
  # ==============================================================================
80
 
81
- # Example JSON for the user to copy
82
- EXAMPLE_JSON = """
83
- [
84
- {
85
- "name": "My Python Decoder API",
86
- "link": "https://huggingface.co/spaces/YOUR_USERNAME/KeyLock-API-Python",
87
- "public_key": "-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy2sWjB1iQ3vK03U7e/9E\\nO6J1K/s0tBq4Pz8F3r9/i8s7t9R1p8Z4Y6h4f4O7w9p9Z0c8t7m4J1e9g7K9m6f3\\nR1k3y7v1w0l7z6s5v2l8l4t9v8z7y6t5k2x1c9v7z3k1y9w8r5t3s1v9a8d7g6f5\\ne4d3c2b1a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2\\nc1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0\\na9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8\\ne7d6\\n-----END PUBLIC KEY-----"
88
- }
89
- ]
90
- """
91
-
92
  with gr.Blocks(theme=gr.themes.Soft(), title="KeyLock Image Creator") as demo:
93
  gr.Markdown("# 🏭 KeyLock Image Creator")
94
- gr.Markdown("Create secure, encrypted images by providing a JSON configuration of decoder endpoints and your secret data.")
95
-
96
- with gr.Row():
97
- with gr.Column(scale=2):
98
- gr.Markdown("### 1. Configure Decoder Services")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  json_config = gr.Textbox(
100
- lines=10,
101
  label="JSON Configuration",
102
- placeholder="Paste your JSON list of services here...",
103
- info="Each service must have a 'name', 'link', and 'public_key'."
104
- )
105
- gr.Markdown("### 2. Enter Your Secret Data")
106
- secret_data = gr.Textbox(
107
- lines=5,
108
- label="Secret Data (Key-Value Pairs)",
109
- placeholder="USERNAME: [email protected]\nAPI_KEY: sk-12345..."
110
- )
111
-
112
- with gr.Column(scale=1):
113
- gr.Markdown("### 3. Select Service and Create")
114
- service_dropdown = gr.Dropdown(
115
- label="Decoder Service (Paste JSON config)",
116
- choices=[],
117
- interactive=True
118
  )
119
- create_button = gr.Button("Create Encrypted Image", variant="primary")
120
-
121
- status_output = gr.Textbox(label="Status", interactive=False)
122
- image_output = gr.Image(label="Generated Image", type="pil")
123
- download_output = gr.File(label="Download Image")
124
 
125
  # --- Interactivity ---
126
- # When the JSON config textbox changes, update the dropdown menu
127
- json_config.change(
128
- fn=update_service_dropdown,
129
  inputs=[json_config],
130
- outputs=[service_dropdown, status_output]
 
 
 
 
 
 
131
  )
132
 
133
- # When the create button is clicked, run the main generation function
134
  create_button.click(
135
  fn=generate_image,
136
- inputs=[json_config, service_dropdown, secret_data],
137
  outputs=[image_output, download_output, status_output]
138
  )
139
-
140
- with gr.Accordion("Example JSON Configuration", open=False):
141
- gr.Code(value=EXAMPLE_JSON, language="json")
142
 
143
  if __name__ == "__main__":
144
  demo.launch()
 
2
  import json
3
  import logging
4
  import tempfile
5
+ import core # Import our core module
6
 
7
  # --- Configure Logging ---
8
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
9
  logger = logging.getLogger(__name__)
10
 
11
  # ==============================================================================
12
+ # CONFIGURATION & DATA HANDLING
13
  # ==============================================================================
14
 
15
+ ENDPOINTS = []
16
+ ENDPOINT_NAMES = []
17
+ DEFAULT_CONFIG_FILE = "endpoints.json"
 
18
 
19
+ def load_config_from_string(json_string: str) -> str:
20
+ """Parses a JSON string, updates global config, and returns a status message."""
21
+ global ENDPOINTS, ENDPOINT_NAMES
22
  try:
23
+ data = json.loads(json_string)
24
+ if not isinstance(data, list):
25
+ raise TypeError("JSON root must be a list of service objects.")
26
+
27
+ # Validate and extract data
28
+ temp_endpoints = []
29
+ temp_names = []
30
+ for service in data:
31
  if not all(k in service for k in ("name", "link", "public_key")):
32
  raise ValueError("Each service object must contain 'name', 'link', and 'public_key' keys.")
33
+ temp_endpoints.append(service)
34
+ temp_names.append(service["name"])
35
+
36
+ ENDPOINTS = temp_endpoints
37
+ ENDPOINT_NAMES = temp_names
38
 
39
+ status_msg = f"βœ… Success! Loaded {len(ENDPOINT_NAMES)} services from custom JSON."
40
+ logger.info(status_msg)
41
+ return status_msg
42
+ except Exception as e:
43
+ status_msg = f"❌ Error loading custom JSON: {e}"
44
+ logger.error(status_msg)
45
+ # On error, we don't change the existing valid configuration
46
+ return status_msg
47
+
48
+ # --- Load initial configuration from the file at startup ---
49
+ try:
50
+ with open(DEFAULT_CONFIG_FILE, "r") as f:
51
+ initial_config = f.read()
52
+ INITIAL_STATUS = load_config_from_string(initial_config)
53
+ except FileNotFoundError:
54
+ logger.warning(f"{DEFAULT_CONFIG_FILE} not found. App will start with an empty configuration.")
55
+ initial_config = "[]"
56
+ INITIAL_STATUS = f"Status: {DEFAULT_CONFIG_FILE} not found. Please provide a configuration."
57
+ except Exception as e:
58
+ logger.error(f"Error loading initial config file: {e}")
59
+ initial_config = "[]"
60
+ INITIAL_STATUS = f"Status: Error loading {DEFAULT_CONFIG_FILE}: {e}"
61
+
62
+ # ==============================================================================
63
+ # MAIN APPLICATION LOGIC
64
+ # ==============================================================================
65
+
66
+ def generate_image(selected_service_name: str, secret_data_str: str):
67
  """
68
  The main function called by the 'Create' button.
69
  It finds the selected service's public key and calls the core encryption logic.
70
  """
71
+ if not all([selected_service_name, secret_data_str]):
72
+ raise gr.Error("A selected service and secret data are both required.")
73
 
74
  try:
75
+ # Find the public key for the selected service from the loaded global config
 
 
76
  public_key = None
77
+ for service in ENDPOINTS:
78
  if service.get("name") == selected_service_name:
79
  public_key = service.get("public_key")
80
  break
81
 
82
  if not public_key:
83
+ raise gr.Error(f"Could not find the service '{selected_service_name}'. Please check the configuration.")
84
 
85
  # Call the core function to do the heavy lifting
86
  encrypted_image = core.create_encrypted_image(secret_data_str, public_key)
 
93
 
94
  except Exception as e:
95
  logger.error(f"Image creation failed: {e}", exc_info=True)
 
96
  return None, None, f"❌ Error: {e}"
97
 
98
  # ==============================================================================
99
  # GRADIO INTERFACE
100
  # ==============================================================================
101
 
 
 
 
 
 
 
 
 
 
 
 
102
  with gr.Blocks(theme=gr.themes.Soft(), title="KeyLock Image Creator") as demo:
103
  gr.Markdown("# 🏭 KeyLock Image Creator")
104
+ gr.Markdown("Create secure, encrypted PNG images for various decoder services.")
105
+
106
+ # This invisible textbox holds the current state of the dropdown choices
107
+ # This allows us to update the dropdown on the main tab from the config tab
108
+ dropdown_state = gr.State(ENDPOINT_NAMES)
109
+
110
+ with gr.Tabs():
111
+ with gr.TabItem("Create Image", id=0):
112
+ with gr.Row():
113
+ with gr.Column(scale=2):
114
+ gr.Markdown("### 1. Select Decoder Service")
115
+ service_dropdown = gr.Dropdown(
116
+ label="Target Service",
117
+ choices=ENDPOINT_NAMES,
118
+ value=ENDPOINT_NAMES[0] if ENDPOINT_NAMES else None,
119
+ interactive=True
120
+ )
121
+ gr.Markdown("### 2. Enter Your Secret Data")
122
+ secret_data = gr.Textbox(
123
+ lines=7,
124
+ label="Secret Data (Key-Value Pairs)",
125
+ placeholder="USERNAME: [email protected]\nAPI_KEY: sk-12345..."
126
+ )
127
+ with gr.Column(scale=1):
128
+ gr.Markdown("### 3. Generate")
129
+ create_button = gr.Button("Create Encrypted Image", variant="primary")
130
+ status_output = gr.Textbox(label="Status", interactive=False)
131
+ image_output = gr.Image(label="Generated Image", type="pil")
132
+ download_output = gr.File(label="Download Image")
133
+
134
+ with gr.TabItem("Manage Configuration", id=1):
135
+ gr.Markdown("## Decoder Service Configuration")
136
+ gr.Markdown("You can override the default `endpoints.json` by pasting a new JSON configuration below.")
137
  json_config = gr.Textbox(
138
+ lines=15,
139
  label="JSON Configuration",
140
+ value=initial_config,
141
+ info="Each service must be an object in a list with 'name', 'link', and 'public_key'."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  )
143
+ update_config_button = gr.Button("Apply New Configuration")
144
+ config_status_output = gr.Textbox(label="Configuration Status", value=INITIAL_STATUS, interactive=False)
 
 
 
145
 
146
  # --- Interactivity ---
147
+ # When the "Apply" button on the config tab is clicked...
148
+ update_config_button.click(
149
+ fn=load_config_from_string,
150
  inputs=[json_config],
151
+ outputs=[config_status_output]
152
+ ).then(
153
+ # ...then, update the dropdown on the main tab with the new choices.
154
+ # We use a lambda to get the latest global state.
155
+ lambda: gr.Dropdown(choices=ENDPOINT_NAMES, value=ENDPOINT_NAMES[0] if ENDPOINT_NAMES else None),
156
+ inputs=[],
157
+ outputs=[service_dropdown]
158
  )
159
 
160
+ # When the "Create" button on the main tab is clicked...
161
  create_button.click(
162
  fn=generate_image,
163
+ inputs=[service_dropdown, secret_data],
164
  outputs=[image_output, download_output, status_output]
165
  )
 
 
 
166
 
167
  if __name__ == "__main__":
168
  demo.launch()