Update client/app.py
Browse files- client/app.py +64 -82
client/app.py
CHANGED
@@ -7,9 +7,9 @@ import logging
|
|
7 |
import os
|
8 |
import requests
|
9 |
import struct
|
|
|
10 |
import numpy as np
|
11 |
from cryptography.hazmat.primitives import serialization
|
12 |
-
# Import 'rsa' for key generation
|
13 |
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
14 |
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
15 |
from cryptography.hazmat.primitives import hashes
|
@@ -63,32 +63,14 @@ def prepare_base_image(uploaded_image: Image.Image | None, progress) -> Image.Im
|
|
63 |
# --- Core Cryptography and Image Creation (Unchanged) ---
|
64 |
|
65 |
def generate_rsa_keys():
|
66 |
-
|
67 |
-
private_key =
|
68 |
-
|
69 |
-
key_size=2048
|
70 |
-
)
|
71 |
-
private_pem = private_key.private_bytes(
|
72 |
-
encoding=serialization.Encoding.PEM,
|
73 |
-
format=serialization.PrivateFormat.PKCS8,
|
74 |
-
encryption_algorithm=serialization.NoEncryption()
|
75 |
-
).decode('utf-8')
|
76 |
-
public_pem = private_key.public_key().public_bytes(
|
77 |
-
encoding=serialization.Encoding.PEM,
|
78 |
-
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
79 |
-
).decode('utf-8')
|
80 |
return private_pem, public_pem
|
81 |
|
82 |
-
def create_encrypted_image(
|
83 |
-
secret_data_str: str,
|
84 |
-
public_key_pem: str,
|
85 |
-
base_image: Image.Image,
|
86 |
-
overlay_option: str
|
87 |
-
) -> Image.Image:
|
88 |
-
"""Encrypts data and embeds it into an image using LSB steganography."""
|
89 |
if not secret_data_str.strip(): raise ValueError("Secret data cannot be empty.")
|
90 |
if not public_key_pem.strip(): raise ValueError("Public Key cannot be empty.")
|
91 |
-
|
92 |
data_dict = {}
|
93 |
for line in secret_data_str.splitlines():
|
94 |
line = line.strip()
|
@@ -96,18 +78,15 @@ def create_encrypted_image(
|
|
96 |
parts = line.split(':', 1) if ':' in line else line.split('=', 1)
|
97 |
if len(parts) == 2: data_dict[parts[0].strip()] = parts[1].strip().strip("'\"")
|
98 |
if not data_dict: raise ValueError("No valid key-value pairs found.")
|
99 |
-
|
100 |
json_bytes = json.dumps(data_dict).encode('utf-8')
|
101 |
public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))
|
102 |
aes_key, nonce = os.urandom(32), os.urandom(12)
|
103 |
ciphertext = AESGCM(aes_key).encrypt(nonce, json_bytes, None)
|
104 |
rsa_encrypted_key = public_key.encrypt(aes_key, padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
|
105 |
-
|
106 |
encrypted_payload = struct.pack('>I', len(rsa_encrypted_key)) + rsa_encrypted_key + nonce + ciphertext
|
107 |
img = base_image.copy().convert("RGB")
|
108 |
width, height = img.size
|
109 |
-
|
110 |
-
# Stylish Overlays (Code unchanged)
|
111 |
draw = ImageDraw.Draw(img, "RGBA")
|
112 |
try:
|
113 |
font_bold = ImageFont.truetype("DejaVuSans-Bold.ttf", 30)
|
@@ -128,22 +107,17 @@ def create_encrypted_image(
|
|
128 |
draw.rectangle([20, box_y0, width - 20, height - 20], fill=overlay_color)
|
129 |
current_y = box_y0 + 15
|
130 |
for i, (key, value) in enumerate(data_dict.items()):
|
131 |
-
if overlay_option == "Keys Only":
|
132 |
-
draw.text((35, current_y), key, fill=key_color, font=font_regular)
|
133 |
else:
|
134 |
key_text = f"{key}:"; draw.text((35, current_y), key_text, fill=key_color, font=font_regular)
|
135 |
key_bbox = draw.textbbox((35, current_y), key_text, font=font_regular)
|
136 |
draw.text((key_bbox[2] + 8, current_y), str(value), fill=value_color, font=font_regular)
|
137 |
current_y += line_heights[i] + 6
|
138 |
-
|
139 |
-
# Steganography
|
140 |
pixel_data = np.array(img.convert("RGB")).ravel()
|
141 |
header = struct.pack('>I', len(encrypted_payload))
|
142 |
binary_payload = ''.join(format(b, '08b') for b in header + encrypted_payload)
|
143 |
-
|
144 |
-
if len(binary_payload) > pixel_data.size:
|
145 |
-
raise ValueError(f"Data is too large for the image. Max bytes: {pixel_data.size // 8}.")
|
146 |
-
|
147 |
pixel_data[:len(binary_payload)] = (pixel_data[:len(binary_payload)] & 0xFE) | np.array(list(binary_payload), dtype=np.uint8)
|
148 |
stego_pixels = pixel_data.reshape((height, width, 3))
|
149 |
return Image.fromarray(stego_pixels, 'RGB')
|
@@ -151,70 +125,80 @@ def create_encrypted_image(
|
|
151 |
# --- New Server Management and Gradio Wrappers ---
|
152 |
|
153 |
def add_server(url: str, current_servers: list):
|
154 |
-
|
155 |
-
if not url or not url.startswith(('http://', 'https://')):
|
156 |
-
raise gr.Error("Please enter a valid server URL (e.g., https://...).")
|
157 |
-
|
158 |
url = url.strip().rstrip('/')
|
159 |
if any(s['url'] == url for s in current_servers):
|
160 |
-
|
161 |
-
|
162 |
status = f"⏳ Testing server at {url}..."
|
163 |
yield current_servers, status, gr.Button(interactive=False)
|
164 |
-
|
165 |
try:
|
166 |
client = Client(url, verbose=False)
|
167 |
-
|
168 |
-
|
169 |
-
with open(info_result_path, 'r', encoding='utf-8') as f: server_info = json.load(f)
|
170 |
server_name = server_info.get("name", url)
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
if not public_key.strip().startswith("-----BEGIN PUBLIC KEY-----"):
|
176 |
-
raise ValueError("Received invalid public key format from server.")
|
177 |
-
|
178 |
-
new_server = {
|
179 |
-
"name": f"{server_name} ({url.split('//')[1].split('/')[0]})",
|
180 |
-
"url": url,
|
181 |
-
"public_key": public_key,
|
182 |
-
"api_endpoint": "/keylock-server"
|
183 |
-
}
|
184 |
updated_servers = current_servers + [new_server]
|
185 |
-
status_message = f"✅ Successfully added server: {server_name}"
|
186 |
yield updated_servers, status_message, gr.Button(interactive=True)
|
187 |
-
|
188 |
except Exception as e:
|
189 |
logger.error(f"Failed to add server at {url}: {e}", exc_info=True)
|
190 |
-
status_message = f"❌ Failed to connect or validate server at {url}. Error: {e}"
|
191 |
yield current_servers, status_message, gr.Button(interactive=True)
|
192 |
|
193 |
def create_keylock_wrapper(service_name: str, secret_data: str, available_servers: list, uploaded_image: Image.Image | None, overlay_option: str, progress=gr.Progress(track_tqdm=True)):
|
|
|
|
|
|
|
|
|
|
|
194 |
if not service_name: raise gr.Error("Please add and select a target server.")
|
195 |
server = next((s for s in available_servers if s['name'] == service_name), None)
|
196 |
if not server: raise gr.Error(f"Could not find config for '{service_name}'. Please re-add it.")
|
|
|
|
|
197 |
try:
|
198 |
base_image = prepare_base_image(uploaded_image, progress)
|
199 |
progress(0.5, desc="Encrypting and embedding data...")
|
200 |
created_image = create_encrypted_image(secret_data, server['public_key'], base_image, overlay_option)
|
201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
except Exception as e:
|
203 |
logger.error(f"Error creating image: {e}", exc_info=True)
|
204 |
-
|
|
|
|
|
|
|
205 |
|
206 |
-
def send_keylock_wrapper(service_name: str,
|
|
|
207 |
if not service_name: raise gr.Error("Please select a target server.")
|
208 |
-
if
|
|
|
209 |
server = next((s for s in available_servers if s['name'] == service_name), None)
|
210 |
if not server: raise gr.Error(f"Config Error for '{service_name}'. Please re-add it.")
|
211 |
|
212 |
server_url, api_name = server['url'], server['api_endpoint']
|
213 |
yield None, f"⏳ Connecting to remote server: {server_url}"
|
214 |
try:
|
215 |
-
|
216 |
-
|
217 |
-
|
|
|
|
|
218 |
client = Client(server_url)
|
219 |
result = client.predict(image_base64_string=b64_string, api_name=api_name)
|
220 |
|
@@ -252,13 +236,16 @@ with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
|
|
252 |
creator_overlay_option = gr.Radio(label="Overlay Content", choices=["Keys and Values", "Keys Only", "None"], value="Keys and Values")
|
253 |
creator_button = gr.Button("✨ Create Auth Image", variant="primary")
|
254 |
with gr.Column(scale=3):
|
255 |
-
|
|
|
|
|
256 |
|
257 |
with gr.TabItem("② Send KeyLock", id=1):
|
258 |
with gr.Row(variant="panel"):
|
259 |
with gr.Column(scale=1):
|
260 |
send_service_dropdown = gr.Dropdown(label="Target Server", interactive=True)
|
261 |
-
|
|
|
262 |
client_button = gr.Button("🔓 Decrypt via Remote Server", variant="primary")
|
263 |
with gr.Column(scale=1):
|
264 |
client_json_output = gr.JSON(label="Decrypted Data from Server")
|
@@ -279,24 +266,16 @@ with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
|
|
279 |
dropdown_update = gr.Dropdown(choices=server_names, value=selected, label="Target Server", interactive=True)
|
280 |
return dropdown_update, dropdown_update
|
281 |
|
282 |
-
add_server_button.click(
|
283 |
-
fn=add_server,
|
284 |
-
inputs=[server_url_input, servers_state],
|
285 |
-
outputs=[servers_state, global_status, add_server_button]
|
286 |
-
).then(fn=lambda: "", outputs=[server_url_input]) # Clear input after trying
|
287 |
|
288 |
-
servers_state.change(
|
289 |
-
fn=update_dropdowns_from_state,
|
290 |
-
inputs=servers_state,
|
291 |
-
outputs=[creator_service_dropdown, send_service_dropdown]
|
292 |
-
)
|
293 |
|
294 |
gen_keys_button.click(fn=generate_rsa_keys, inputs=None, outputs=[output_private_key, output_public_key])
|
295 |
|
296 |
creator_button.click(
|
297 |
fn=create_keylock_wrapper,
|
298 |
inputs=[creator_service_dropdown, creator_secret_input, servers_state, creator_base_image_input, creator_overlay_option],
|
299 |
-
outputs=[
|
300 |
)
|
301 |
|
302 |
client_button.click(
|
@@ -307,7 +286,10 @@ with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
|
|
307 |
|
308 |
creator_service_dropdown.change(fn=sync_dropdowns, inputs=creator_service_dropdown, outputs=send_service_dropdown)
|
309 |
send_service_dropdown.change(fn=sync_dropdowns, inputs=send_service_dropdown, outputs=creator_service_dropdown)
|
310 |
-
|
|
|
|
|
|
|
311 |
|
312 |
if __name__ == "__main__":
|
313 |
demo.launch()
|
|
|
7 |
import os
|
8 |
import requests
|
9 |
import struct
|
10 |
+
import tempfile # <--- Import tempfile
|
11 |
import numpy as np
|
12 |
from cryptography.hazmat.primitives import serialization
|
|
|
13 |
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
14 |
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
15 |
from cryptography.hazmat.primitives import hashes
|
|
|
63 |
# --- Core Cryptography and Image Creation (Unchanged) ---
|
64 |
|
65 |
def generate_rsa_keys():
|
66 |
+
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
67 |
+
private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()).decode('utf-8')
|
68 |
+
public_pem = private_key.public_key().public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo).decode('utf-8')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
return private_pem, public_pem
|
70 |
|
71 |
+
def create_encrypted_image(secret_data_str: str, public_key_pem: str, base_image: Image.Image, overlay_option: str) -> Image.Image:
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
if not secret_data_str.strip(): raise ValueError("Secret data cannot be empty.")
|
73 |
if not public_key_pem.strip(): raise ValueError("Public Key cannot be empty.")
|
|
|
74 |
data_dict = {}
|
75 |
for line in secret_data_str.splitlines():
|
76 |
line = line.strip()
|
|
|
78 |
parts = line.split(':', 1) if ':' in line else line.split('=', 1)
|
79 |
if len(parts) == 2: data_dict[parts[0].strip()] = parts[1].strip().strip("'\"")
|
80 |
if not data_dict: raise ValueError("No valid key-value pairs found.")
|
|
|
81 |
json_bytes = json.dumps(data_dict).encode('utf-8')
|
82 |
public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))
|
83 |
aes_key, nonce = os.urandom(32), os.urandom(12)
|
84 |
ciphertext = AESGCM(aes_key).encrypt(nonce, json_bytes, None)
|
85 |
rsa_encrypted_key = public_key.encrypt(aes_key, padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
|
|
|
86 |
encrypted_payload = struct.pack('>I', len(rsa_encrypted_key)) + rsa_encrypted_key + nonce + ciphertext
|
87 |
img = base_image.copy().convert("RGB")
|
88 |
width, height = img.size
|
89 |
+
# (Overlay drawing code is unchanged and correct)
|
|
|
90 |
draw = ImageDraw.Draw(img, "RGBA")
|
91 |
try:
|
92 |
font_bold = ImageFont.truetype("DejaVuSans-Bold.ttf", 30)
|
|
|
107 |
draw.rectangle([20, box_y0, width - 20, height - 20], fill=overlay_color)
|
108 |
current_y = box_y0 + 15
|
109 |
for i, (key, value) in enumerate(data_dict.items()):
|
110 |
+
if overlay_option == "Keys Only": draw.text((35, current_y), key, fill=key_color, font=font_regular)
|
|
|
111 |
else:
|
112 |
key_text = f"{key}:"; draw.text((35, current_y), key_text, fill=key_color, font=font_regular)
|
113 |
key_bbox = draw.textbbox((35, current_y), key_text, font=font_regular)
|
114 |
draw.text((key_bbox[2] + 8, current_y), str(value), fill=value_color, font=font_regular)
|
115 |
current_y += line_heights[i] + 6
|
116 |
+
# (Steganography code is unchanged and correct)
|
|
|
117 |
pixel_data = np.array(img.convert("RGB")).ravel()
|
118 |
header = struct.pack('>I', len(encrypted_payload))
|
119 |
binary_payload = ''.join(format(b, '08b') for b in header + encrypted_payload)
|
120 |
+
if len(binary_payload) > pixel_data.size: raise ValueError(f"Data is too large. Max: {pixel_data.size // 8} bytes.")
|
|
|
|
|
|
|
121 |
pixel_data[:len(binary_payload)] = (pixel_data[:len(binary_payload)] & 0xFE) | np.array(list(binary_payload), dtype=np.uint8)
|
122 |
stego_pixels = pixel_data.reshape((height, width, 3))
|
123 |
return Image.fromarray(stego_pixels, 'RGB')
|
|
|
125 |
# --- New Server Management and Gradio Wrappers ---
|
126 |
|
127 |
def add_server(url: str, current_servers: list):
|
128 |
+
# This function is correct from the previous fix. Unchanged.
|
129 |
+
if not url or not url.startswith(('http://', 'https://')): raise gr.Error("Please enter a valid server URL (e.g., https://...).")
|
|
|
|
|
130 |
url = url.strip().rstrip('/')
|
131 |
if any(s['url'] == url for s in current_servers):
|
132 |
+
gr.Info(f"Server at {url} is already in the list.")
|
133 |
+
return current_servers, f"ℹ️ Server at {url} is already in the list.", gr.Button(interactive=True)
|
134 |
status = f"⏳ Testing server at {url}..."
|
135 |
yield current_servers, status, gr.Button(interactive=False)
|
|
|
136 |
try:
|
137 |
client = Client(url, verbose=False)
|
138 |
+
server_info = client.predict(api_name="/keylock-info")
|
139 |
+
if not isinstance(server_info, dict): raise TypeError(f"Expected dict from /keylock-info, but got {type(server_info)}")
|
|
|
140 |
server_name = server_info.get("name", url)
|
141 |
+
public_key = client.predict(api_name="/keylock-pub")
|
142 |
+
if not isinstance(public_key, str): raise TypeError(f"Expected a string from /keylock-pub, but got {type(public_key)}")
|
143 |
+
if not public_key.strip().startswith("-----BEGIN PUBLIC KEY-----"): raise ValueError("Received invalid public key format from server.")
|
144 |
+
new_server = {"name": f"{server_name} ({url.split('//')[1].split('/')[0]})", "url": url, "public_key": public_key, "api_endpoint": "/keylock-server"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
updated_servers = current_servers + [new_server]
|
146 |
+
status_message = f"✅ Successfully added server: {server_name}"; gr.Info(status_message)
|
147 |
yield updated_servers, status_message, gr.Button(interactive=True)
|
|
|
148 |
except Exception as e:
|
149 |
logger.error(f"Failed to add server at {url}: {e}", exc_info=True)
|
150 |
+
status_message = f"❌ Failed to connect or validate server at {url}. Error: {e}"; gr.Error(status_message)
|
151 |
yield current_servers, status_message, gr.Button(interactive=True)
|
152 |
|
153 |
def create_keylock_wrapper(service_name: str, secret_data: str, available_servers: list, uploaded_image: Image.Image | None, overlay_option: str, progress=gr.Progress(track_tqdm=True)):
|
154 |
+
"""
|
155 |
+
Creates the encrypted image and returns it to two separate components:
|
156 |
+
1. A gr.Image for visual preview (which may be re-encoded by Gradio).
|
157 |
+
2. A gr.File for a guaranteed, uncorrupted PNG download.
|
158 |
+
"""
|
159 |
if not service_name: raise gr.Error("Please add and select a target server.")
|
160 |
server = next((s for s in available_servers if s['name'] == service_name), None)
|
161 |
if not server: raise gr.Error(f"Could not find config for '{service_name}'. Please re-add it.")
|
162 |
+
|
163 |
+
png_path = None # Initialize to handle potential errors
|
164 |
try:
|
165 |
base_image = prepare_base_image(uploaded_image, progress)
|
166 |
progress(0.5, desc="Encrypting and embedding data...")
|
167 |
created_image = create_encrypted_image(secret_data, server['public_key'], base_image, overlay_option)
|
168 |
+
|
169 |
+
# Save image to a temporary PNG file to prevent re-encoding.
|
170 |
+
# This temporary file will be served by gr.File for perfect data integrity.
|
171 |
+
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_f:
|
172 |
+
png_path = temp_f.name
|
173 |
+
created_image.save(png_path, "PNG", compress_level=1)
|
174 |
+
|
175 |
+
status_message = f"✅ Success! Image created for '{service_name}'. Use the link to download the uncorrupted file."
|
176 |
+
# Return path to both preview and file components. Make file component visible.
|
177 |
+
return png_path, png_path, status_message, gr.Tabs(selected=1), gr.File(visible=True)
|
178 |
+
|
179 |
except Exception as e:
|
180 |
logger.error(f"Error creating image: {e}", exc_info=True)
|
181 |
+
if png_path and os.path.exists(png_path):
|
182 |
+
os.remove(png_path) # Clean up temp file on error
|
183 |
+
# Return Nones and hide file component
|
184 |
+
return None, None, f"❌ Error: {e}", gr.Tabs(), gr.File(visible=False)
|
185 |
|
186 |
+
def send_keylock_wrapper(service_name: str, image_path: str, available_servers: list):
|
187 |
+
# This function now expects a file path `image_path` from gr.Image
|
188 |
if not service_name: raise gr.Error("Please select a target server.")
|
189 |
+
if image_path is None: raise gr.Error("Please create or upload an image to send.")
|
190 |
+
|
191 |
server = next((s for s in available_servers if s['name'] == service_name), None)
|
192 |
if not server: raise gr.Error(f"Config Error for '{service_name}'. Please re-add it.")
|
193 |
|
194 |
server_url, api_name = server['url'], server['api_endpoint']
|
195 |
yield None, f"⏳ Connecting to remote server: {server_url}"
|
196 |
try:
|
197 |
+
# The input `image_path` is a path to a temporary file created by gr.Image.
|
198 |
+
# We read it directly to ensure we get the correct bytes.
|
199 |
+
with open(image_path, "rb") as img_file:
|
200 |
+
b64_string = base64.b64encode(img_file.read()).decode("utf-8")
|
201 |
+
|
202 |
client = Client(server_url)
|
203 |
result = client.predict(image_base64_string=b64_string, api_name=api_name)
|
204 |
|
|
|
236 |
creator_overlay_option = gr.Radio(label="Overlay Content", choices=["Keys and Values", "Keys Only", "None"], value="Keys and Values")
|
237 |
creator_button = gr.Button("✨ Create Auth Image", variant="primary")
|
238 |
with gr.Column(scale=3):
|
239 |
+
# Use two components: one for preview, one for lossless download
|
240 |
+
creator_image_preview = gr.Image(label="Image Preview (for display only)", type="filepath", height=500)
|
241 |
+
creator_file_output = gr.File(label="Download Uncorrupted PNG File", file_count="single", visible=False)
|
242 |
|
243 |
with gr.TabItem("② Send KeyLock", id=1):
|
244 |
with gr.Row(variant="panel"):
|
245 |
with gr.Column(scale=1):
|
246 |
send_service_dropdown = gr.Dropdown(label="Target Server", interactive=True)
|
247 |
+
# This component will receive the file path from the preview image
|
248 |
+
client_image_input = gr.Image(type="filepath", label="Upload or Drag Encrypted Image", sources=["upload", "clipboard"])
|
249 |
client_button = gr.Button("🔓 Decrypt via Remote Server", variant="primary")
|
250 |
with gr.Column(scale=1):
|
251 |
client_json_output = gr.JSON(label="Decrypted Data from Server")
|
|
|
266 |
dropdown_update = gr.Dropdown(choices=server_names, value=selected, label="Target Server", interactive=True)
|
267 |
return dropdown_update, dropdown_update
|
268 |
|
269 |
+
add_server_button.click(fn=add_server, inputs=[server_url_input, servers_state], outputs=[servers_state, global_status, add_server_button]).then(fn=lambda: "", outputs=[server_url_input])
|
|
|
|
|
|
|
|
|
270 |
|
271 |
+
servers_state.change(fn=update_dropdowns_from_state, inputs=servers_state, outputs=[creator_service_dropdown, send_service_dropdown])
|
|
|
|
|
|
|
|
|
272 |
|
273 |
gen_keys_button.click(fn=generate_rsa_keys, inputs=None, outputs=[output_private_key, output_public_key])
|
274 |
|
275 |
creator_button.click(
|
276 |
fn=create_keylock_wrapper,
|
277 |
inputs=[creator_service_dropdown, creator_secret_input, servers_state, creator_base_image_input, creator_overlay_option],
|
278 |
+
outputs=[creator_image_preview, creator_file_output, global_status, tabs, creator_file_output]
|
279 |
)
|
280 |
|
281 |
client_button.click(
|
|
|
286 |
|
287 |
creator_service_dropdown.change(fn=sync_dropdowns, inputs=creator_service_dropdown, outputs=send_service_dropdown)
|
288 |
send_service_dropdown.change(fn=sync_dropdowns, inputs=send_service_dropdown, outputs=creator_service_dropdown)
|
289 |
+
|
290 |
+
# When a new image preview is generated, auto-populate it in the sender tab.
|
291 |
+
# This works because both components now use file paths.
|
292 |
+
creator_image_preview.change(fn=lambda x: x, inputs=creator_image_preview, outputs=client_image_input)
|
293 |
|
294 |
if __name__ == "__main__":
|
295 |
demo.launch()
|