Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -12,84 +12,85 @@ logger = logging.getLogger(__name__)
|
|
12 |
# CONFIGURATION & DATA HANDLING
|
13 |
# ==============================================================================
|
14 |
|
15 |
-
|
16 |
-
ENDPOINT_NAMES = []
|
17 |
-
DEFAULT_CONFIG_FILE = "endpoints.json"
|
18 |
|
19 |
-
def
|
20 |
-
"""
|
21 |
-
global ENDPOINTS, ENDPOINT_NAMES
|
22 |
try:
|
23 |
-
|
|
|
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 |
-
|
34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
40 |
logger.info(status_msg)
|
41 |
return status_msg
|
42 |
except Exception as e:
|
43 |
-
status_msg = f"β Error
|
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 |
-
|
50 |
-
|
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 |
-
#
|
64 |
# ==============================================================================
|
65 |
|
66 |
-
def
|
67 |
-
"""
|
68 |
-
|
69 |
-
|
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 |
-
|
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
|
92 |
-
return encrypted_image, tmp_file.name, f"β
Success! Image created for '{selected_service_name}'.
|
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 |
-
|
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=
|
118 |
-
value=
|
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("
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
|
146 |
# --- Interactivity ---
|
147 |
-
|
148 |
-
|
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()
|