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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -80
app.py CHANGED
@@ -12,84 +12,85 @@ logger = logging.getLogger(__name__)
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)
87
 
88
- # Save to a temporary file for download
89
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
90
  encrypted_image.save(tmp_file.name)
91
- logger.info(f"Generated image saved to temporary file: {tmp_file.name}")
92
- return encrypted_image, tmp_file.name, f"βœ… Success! Image created for '{selected_service_name}'. You can now download it."
93
 
94
  except Exception as e:
95
  logger.error(f"Image creation failed: {e}", exc_info=True)
@@ -100,22 +101,22 @@ def generate_image(selected_service_name: str, secret_data_str: str):
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")
@@ -127,42 +128,65 @@ with gr.Blocks(theme=gr.themes.Soft(), title="KeyLock Image Creator") as demo:
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()
 
12
  # CONFIGURATION & DATA HANDLING
13
  # ==============================================================================
14
 
15
+ DEFAULT_CONFIG_FILE = "https://huggingface.co/spaces/Space-Share/bucket/raw/main/endpoints.json"
 
 
16
 
17
+ def load_config_from_file(filepath: str) -> list:
18
+ """Loads and validates the JSON configuration from a file."""
 
19
  try:
20
+ with open(filepath, "r") as f:
21
+ data = json.load(f)
22
  if not isinstance(data, list):
23
  raise TypeError("JSON root must be a list of service objects.")
 
 
 
 
24
  for service in data:
25
  if not all(k in service for k in ("name", "link", "public_key")):
26
  raise ValueError("Each service object must contain 'name', 'link', and 'public_key' keys.")
27
+ logger.info(f"Successfully loaded {len(data)} services from {filepath}.")
28
+ return data
29
+ except FileNotFoundError:
30
+ logger.warning(f"{filepath} not found. Returning empty list.")
31
+ return []
32
+ except Exception as e:
33
+ logger.error(f"Error loading config file {filepath}: {e}")
34
+ # Return an empty list on error to prevent crashing the app
35
+ return []
36
 
37
+ def save_config_to_file(filepath: str, config_data: list) -> str:
38
+ """Saves the current configuration data to a JSON file."""
39
+ try:
40
+ with open(filepath, "w") as f:
41
+ json.dump(config_data, f, indent=2)
42
+ status_msg = f"βœ… Success! Configuration saved to {filepath}."
43
  logger.info(status_msg)
44
  return status_msg
45
  except Exception as e:
46
+ status_msg = f"❌ Error saving configuration: {e}"
47
  logger.error(status_msg)
 
48
  return status_msg
49
 
50
  # --- Load initial configuration from the file at startup ---
51
+ # The 'config_state' will hold our list of dictionaries
52
+ initial_config = load_config_from_file(DEFAULT_CONFIG_FILE)
 
 
 
 
 
 
 
 
 
 
53
 
54
  # ==============================================================================
55
+ # GRADIO UI HELPER FUNCTIONS
56
  # ==============================================================================
57
 
58
+ def add_new_service(current_config: list, name: str, link: str, public_key: str):
59
+ """Adds a new service to the current configuration state."""
60
+ if not all([name, link, public_key]):
61
+ raise gr.Error("All fields (Name, Link, Public Key) are required to add a new service.")
62
+
63
+ new_service = {"name": name, "link": link, "public_key": public_key}
64
+
65
+ # Check for duplicate names
66
+ if any(service['name'] == name for service in current_config):
67
+ raise gr.Error(f"A service with the name '{name}' already exists. Please use a unique name.")
68
+
69
+ updated_config = current_config + [new_service]
70
+
71
+ # Update dropdown choices
72
+ updated_choices = [service['name'] for service in updated_config]
73
+
74
+ # Clear the input forms and provide success feedback
75
+ status_update = f"βœ… '{name}' added. You can now use it in the 'Create Image' tab. Click 'Save to File' to make it permanent."
76
+ return updated_config, gr.Dropdown(choices=updated_choices), status_update, "", "", ""
77
+
78
+ def generate_image(selected_service_name: str, secret_data_str: str, current_config: list):
79
+ """The main image creation function, now takes the config state as an input."""
80
  if not all([selected_service_name, secret_data_str]):
81
  raise gr.Error("A selected service and secret data are both required.")
82
 
83
  try:
84
+ public_key = next((s['public_key'] for s in current_config if s.get('name') == selected_service_name), None)
 
 
 
 
 
 
85
  if not public_key:
86
  raise gr.Error(f"Could not find the service '{selected_service_name}'. Please check the configuration.")
87
 
 
88
  encrypted_image = core.create_encrypted_image(secret_data_str, public_key)
89
 
 
90
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
91
  encrypted_image.save(tmp_file.name)
92
+ logger.info(f"Generated image saved for '{selected_service_name}'.")
93
+ return encrypted_image, tmp_file.name, f"βœ… Success! Image created for '{selected_service_name}'."
94
 
95
  except Exception as e:
96
  logger.error(f"Image creation failed: {e}", exc_info=True)
 
101
  # ==============================================================================
102
 
103
  with gr.Blocks(theme=gr.themes.Soft(), title="KeyLock Image Creator") as demo:
104
+ # This gr.State object holds the current configuration (list of dicts)
105
+ # It's initialized with the data loaded from endpoints.json
106
+ config_state = gr.State(initial_config)
107
+
108
  gr.Markdown("# 🏭 KeyLock Image Creator")
109
  gr.Markdown("Create secure, encrypted PNG images for various decoder services.")
110
 
111
+ with gr.Tabs() as tabs:
 
 
 
 
112
  with gr.TabItem("Create Image", id=0):
113
  with gr.Row():
114
  with gr.Column(scale=2):
115
  gr.Markdown("### 1. Select Decoder Service")
116
  service_dropdown = gr.Dropdown(
117
  label="Target Service",
118
+ choices=[s['name'] for s in initial_config],
119
+ value=[s['name'] for s in initial_config][0] if initial_config else None,
120
  interactive=True
121
  )
122
  gr.Markdown("### 2. Enter Your Secret Data")
 
128
  with gr.Column(scale=1):
129
  gr.Markdown("### 3. Generate")
130
  create_button = gr.Button("Create Encrypted Image", variant="primary")
131
+ status_output = gr.Textbox(label="Status", interactive=False, lines=2)
132
  image_output = gr.Image(label="Generated Image", type="pil")
133
  download_output = gr.File(label="Download Image")
134
 
135
  with gr.TabItem("Manage Configuration", id=1):
136
  gr.Markdown("## Decoder Service Configuration")
137
+ gr.Markdown("View the current configuration, add new services, and save your changes.")
138
+
139
+ with gr.Row():
140
+ with gr.Column(scale=2):
141
+ gr.Markdown("### Current Configuration")
142
+ config_display = gr.JSON(value=initial_config, label="Loaded from endpoints.json")
143
+
144
+ with gr.Column(scale=1):
145
+ gr.Markdown("### Add a New Service")
146
+ new_service_name = gr.Textbox(label="Service Name", placeholder="e.g., My Production API")
147
+ new_service_link = gr.Textbox(label="Service Link", placeholder="https://huggingface.co/...")
148
+ new_service_key = gr.Textbox(label="Public Key (PEM Format)", lines=5, placeholder="-----BEGIN PUBLIC KEY-----...")
149
+ add_service_button = gr.Button("Add Service to Current Session")
150
+
151
+ gr.Markdown("---")
152
+ save_config_button = gr.Button("Save Current Configuration to File", variant="secondary")
153
+ config_status_output = gr.Textbox(label="Configuration Status", interactive=False)
154
 
155
  # --- Interactivity ---
156
+
157
+ # Main image creation logic
 
 
 
 
 
 
 
 
 
 
 
 
158
  create_button.click(
159
  fn=generate_image,
160
+ inputs=[service_dropdown, secret_data, config_state],
161
  outputs=[image_output, download_output, status_output]
162
  )
163
 
164
+ # Logic for the "Manage Configuration" tab
165
+ add_service_button.click(
166
+ fn=add_new_service,
167
+ inputs=[config_state, new_service_name, new_service_link, new_service_key],
168
+ # Outputs: update the state, the dropdown, the status, and clear the form fields
169
+ outputs=[config_state, service_dropdown, config_status_output, new_service_name, new_service_link, new_service_key]
170
+ ).then(
171
+ # After updating the state, also update the JSON display to reflect the change
172
+ lambda state: state,
173
+ inputs=[config_state],
174
+ outputs=[config_display]
175
+ )
176
+
177
+ save_config_button.click(
178
+ fn=save_config_to_file,
179
+ inputs=[gr.State(DEFAULT_CONFIG_FILE), config_state],
180
+ outputs=[config_status_output]
181
+ )
182
+
183
+ # When the configuration state changes (e.g., after adding a service),
184
+ # update the JSON display on the config tab. This keeps the UI in sync.
185
+ config_state.change(
186
+ fn=lambda state: state,
187
+ inputs=[config_state],
188
+ outputs=[config_display]
189
+ )
190
+
191
  if __name__ == "__main__":
192
  demo.launch()