Danisz commited on
Commit
0b2be8e
·
1 Parent(s): e68daaa

Refactor into JS execution

Browse files
Files changed (4) hide show
  1. app.py +6 -283
  2. audio_utils.py +33 -0
  3. flet_app.py +98 -0
  4. js_execution.py +141 -0
app.py CHANGED
@@ -1,299 +1,22 @@
1
  from contextlib import asynccontextmanager
2
-
3
- import flet as ft
4
  import flet.fastapi as flet_fastapi
5
- import sys
6
  import logging
7
- import os
8
- from fastapi import FastAPI, File, UploadFile, HTTPException, Form, Request
9
- from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
10
- from collections import OrderedDict
11
- import base64
12
- from starlette.responses import Response
13
- import asyncio
14
- import time
15
 
16
  # Set up basic logging configuration
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
20
- async def main(page: ft.Page):
21
- page.title = "Simple Audio Recorder"
22
-
23
- # Log the operating system platform
24
- logger.info(f"Operating System: {sys.platform}")
25
-
26
- audio_recorder = ft.AudioRecorder(
27
- audio_encoder=ft.AudioEncoder.OPUS,
28
- on_state_changed=lambda e: update_recording_status(e.data)
29
- )
30
- recording_status = ft.Text()
31
- loading_animation = ft.ProgressRing(visible=False)
32
- audio_player = None # Initialize audio_player as None
33
-
34
- page.overlay.append(audio_recorder) # Only add audio_recorder to overlay initially
35
-
36
- page.update()
37
-
38
- def start_recording(e):
39
- record_button.disabled = True
40
- stop_button.disabled = True
41
- play_button.disabled = True
42
- loading_animation.visible = True
43
- recording_status.value = "Preparing to record..."
44
- page.update()
45
-
46
- logger.info("Starting recording")
47
- audio_recorder.start_recording("")
48
-
49
- # The actual recording start will be handled in update_recording_status
50
-
51
- async def stop_recording_flet(e):
52
- record_button.disabled = False
53
- stop_button.disabled = True
54
-
55
- page.update()
56
- nonlocal audio_player
57
- result_uri = await audio_recorder.stop_recording_async()
58
- logger.info("Result URI: %s", result_uri)
59
-
60
- logger.info("="*100)
61
- logger.info(f"Session ID: {page.session_id}")
62
-
63
- # Use launch_url to load the upload page in the hidden iframe
64
- page.launch_url(f"/upload-blob/?blob_uri={result_uri}&session_id={page.session_id}", web_window_name="hidden-iframe")
65
-
66
- # Wait for the audio data to be stored
67
- start_time = time.time()
68
- await wait_for_audio_data(page.session_id)
69
- end_time = time.time()
70
- elapsed_time = end_time - start_time
71
- logger.info(f"Time taken to wait for audio data: {elapsed_time:.2f} seconds")
72
-
73
- # Create audio_player using the base64 string from audio_storage
74
- if page.session_id in audio_storage:
75
- base64_audio = audio_storage.pop(page.session_id)
76
- audio_player = ft.Audio(src=f"data:audio/wav;base64,{base64_audio}")
77
- page.overlay.append(audio_player)
78
- else:
79
- logger.error(f"Audio data not found for session {page.session_id}")
80
-
81
- play_button.disabled = False
82
- page.update()
83
- logger.info(page.controls)
84
-
85
- def play_recording(e):
86
- if audio_player:
87
- audio_player.play()
88
-
89
- def update_recording_status(status):
90
- if status == "recording":
91
- stop_button.disabled = False
92
- loading_animation.visible = False
93
- recording_status.value = "Recording in progress..."
94
- else:
95
- recording_status.value = f"Recording status: {status}"
96
- page.update()
97
-
98
- record_button = ft.ElevatedButton("Start Recording", on_click=start_recording)
99
- stop_button = ft.ElevatedButton("Stop Recording", on_click=stop_recording_flet, disabled=True)
100
- play_button = ft.ElevatedButton("Play Recording", on_click=play_recording, disabled=True)
101
-
102
- def handle_list_devices(e):
103
- devices = audio_recorder.get_input_devices()
104
- logger.info("Input devices: %s", devices)
105
-
106
- def handle_has_permission(e):
107
- try:
108
- logger.info("HasPermission: %s", audio_recorder.has_permission())
109
- except Exception as e:
110
- logger.error("Error checking permission: %s", str(e))
111
-
112
- def handle_audio_encoding_test(e):
113
- for i in list(ft.AudioEncoder):
114
- logger.info("%s: %s", i, audio_recorder.is_supported_encoder(i))
115
-
116
- page.add(
117
- ft.Text("Simple Audio Recorder", size=24),
118
- ft.Row([record_button, stop_button, play_button, loading_animation]),
119
- recording_status,
120
- ft.ElevatedButton("List Devices", on_click=handle_list_devices),
121
- ft.ElevatedButton("Test AudioEncodings", on_click=handle_audio_encoding_test),
122
- ft.ElevatedButton("Has Permission", on_click=handle_has_permission),
123
- )
124
-
125
- page.update()
126
-
127
-
128
  @asynccontextmanager
129
  async def lifespan(app: FastAPI):
130
  await flet_fastapi.app_manager.start()
131
  yield
132
  await flet_fastapi.app_manager.shutdown()
133
 
134
-
135
  app = FastAPI(lifespan=lifespan)
136
 
137
- # Make sure this line is correct and the path matches what you're using in the iframe src
138
- app.mount("/flet-app", flet_fastapi.app(main))
139
-
140
-
141
- # Directory where files will be saved (current working directory)
142
- UPLOAD_DIRECTORY = os.path.join(os.getcwd(), "uploaded_files")
143
-
144
- @app.get("/", response_class=HTMLResponse)
145
- async def serve_main_page(request: Request):
146
- html_content = """
147
- <!DOCTYPE html>
148
- <html lang="en">
149
- <head>
150
- <meta charset="UTF-8">
151
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
152
- <title>Audio Recorder App</title>
153
- <style>
154
- body, html {
155
- margin: 0;
156
- padding: 0;
157
- height: 100%;
158
- overflow: hidden;
159
- }
160
- #flet-app-frame {
161
- width: 100%;
162
- height: 100%;
163
- border: none;
164
- position: absolute;
165
- top: 0;
166
- left: 0;
167
- }
168
- </style>
169
- </head>
170
- <body>
171
- <iframe id="flet-app-frame"></iframe>
172
- <iframe name="hidden-iframe" style="display:none;"></iframe>
173
- <script>
174
- window.onload = function() {
175
- var iframe = document.getElementById('flet-app-frame');
176
- var currentProtocol = window.location.protocol;
177
- var currentHost = window.location.host;
178
- var src = currentProtocol + '//' + currentHost + '/flet-app/';
179
-
180
- iframe.src = src;
181
- console.log('Iframe src set to:', src);
182
-
183
- // Set Content-Security-Policy based on the current protocol
184
- var meta = document.createElement('meta');
185
- meta.httpEquiv = "Content-Security-Policy";
186
- meta.content = currentProtocol === 'https:' ? "upgrade-insecure-requests" : "";
187
- document.head.appendChild(meta);
188
- };
189
- </script>
190
- </body>
191
- </html>
192
- """
193
- return HTMLResponse(content=html_content)
194
-
195
- @app.get("/upload-blob/", response_class=HTMLResponse)
196
- async def serve_upload_page(blob_uri: str, session_id: str):
197
- logger.info(f"Blob URI: {blob_uri}")
198
- logger.info(f"Session ID: {session_id}")
199
- html_content = f"""
200
- <!DOCTYPE html>
201
- <html lang="en">
202
- <head>
203
- <meta charset="UTF-8">
204
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
205
- <title>Upload Blob</title>
206
- </head>
207
- <body>
208
- <script>
209
- async function sendBlobToServer(blobUri, sessionId) {{
210
- try {{
211
- const blob = await fetch(blobUri).then(r => r.blob());
212
- const base64data = await new Promise((resolve) => {{
213
- const reader = new FileReader();
214
- reader.onloadend = () => resolve(reader.result.split(',')[1]);
215
- reader.readAsDataURL(blob);
216
- }});
217
-
218
- const response = await fetch('/store-audio/', {{
219
- method: 'POST',
220
- headers: {{
221
- 'Content-Type': 'application/json',
222
- }},
223
- body: JSON.stringify({{
224
- session_id: sessionId,
225
- audio_data: base64data
226
- }})
227
- }});
228
-
229
- const result = await response.json();
230
- console.log(result);
231
- }} catch (error) {{
232
- console.error("Error uploading blob:", error);
233
- }}
234
- }}
235
-
236
- sendBlobToServer("{blob_uri}", "{session_id}");
237
- </script>
238
- </body>
239
- </html>
240
- """
241
- return HTMLResponse(content=html_content)
242
-
243
- @app.post("/upload-blob/")
244
- async def upload_blob(file: UploadFile = File(...), session_id: str = Form(...)):
245
- logger.info(f"Uploading file: {file.filename}")
246
- logger.info(f"Session ID: {session_id}")
247
-
248
- # Create a directory for the session if it doesn't exist
249
- session_directory = os.path.join(UPLOAD_DIRECTORY, session_id)
250
- os.makedirs(session_directory, exist_ok=True)
251
-
252
- # Save the file in the session directory
253
- file_location = os.path.join(session_directory, file.filename)
254
- with open(file_location, "wb") as f:
255
- f.write(await file.read())
256
-
257
- return JSONResponse(content={"info": f"file '{file.filename}' saved at '{file_location}'"})
258
-
259
- # Use OrderedDict instead of a regular dictionary
260
- audio_storage = OrderedDict()
261
-
262
- @app.post("/store-audio/")
263
- async def store_audio(data: dict):
264
- session_id = data.get('session_id')
265
- audio_data = data.get('audio_data')
266
-
267
- if not session_id or not audio_data:
268
- raise HTTPException(status_code=400, detail="Missing session_id or audio_data")
269
-
270
- # Store the base64 audio data in memory
271
- audio_storage[session_id] = audio_data
272
-
273
- print(f"Audio data stored for session {session_id}")
274
-
275
- return JSONResponse(content={"info": f"Audio data stored for session {session_id}: {audio_data[:100]}"})
276
-
277
- @app.get("/get-audio/{session_id}")
278
- async def get_audio(session_id: str):
279
- if session_id not in audio_storage:
280
- raise HTTPException(status_code=404, detail="Audio not found")
281
-
282
- # Pop the audio data from the storage
283
- audio_data = audio_storage.pop(session_id)
284
-
285
- return JSONResponse(content={"audio_data": audio_data})
286
- # Add this helper function to wait for the audio data
287
- async def wait_for_audio_data(session_id, max_wait_time=5, initial_interval=0.01):
288
- start_time = time.time()
289
- interval = initial_interval
290
- while time.time() - start_time < max_wait_time:
291
- if session_id in audio_storage:
292
- return
293
- await asyncio.sleep(interval)
294
- # Exponential backoff with a maximum interval of 0.5 seconds
295
- interval = min(interval * 1.5, 0.5)
296
-
297
- # If we've reached this point, we've timed out
298
- logger.error(f"Timeout waiting for audio data for session {session_id}")
299
-
 
1
  from contextlib import asynccontextmanager
 
 
2
  import flet.fastapi as flet_fastapi
 
3
  import logging
4
+ from fastapi import FastAPI
5
+
6
+ from flet_app import main
7
+ from js_execution import mount_flet_app
 
 
 
 
8
 
9
  # Set up basic logging configuration
10
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11
  logger = logging.getLogger(__name__)
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  @asynccontextmanager
14
  async def lifespan(app: FastAPI):
15
  await flet_fastapi.app_manager.start()
16
  yield
17
  await flet_fastapi.app_manager.shutdown()
18
 
 
19
  app = FastAPI(lifespan=lifespan)
20
 
21
+ # Use the new mount_flet_app function with the same arguments
22
+ mount_flet_app(app, "/", flet_fastapi.app(main))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
audio_utils.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import logging
3
+ import flet as ft
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ async def get_base64_audio(page: ft.Page, audio_blob_uri: str) -> str | None:
8
+ start_time = asyncio.get_event_loop().time()
9
+ result = None
10
+
11
+ try:
12
+ logger.info("Result URI: %s", audio_blob_uri)
13
+ logger.info("="*100)
14
+
15
+ # Use the eval_js method to execute JavaScript and get the result directly
16
+ result = await page.eval_js(f"""
17
+ const blob = await fetch('{audio_blob_uri}').then(r => r.blob());
18
+ const base64data = await new Promise((resolve) => {{
19
+ const reader = new FileReader();
20
+ reader.onloadend = () => resolve(reader.result.split(',')[1]);
21
+ reader.readAsDataURL(blob);
22
+ }});
23
+ return base64data;
24
+ """)
25
+
26
+ except Exception as e:
27
+ logger.error(f"Error in get_base64_audio: {str(e)}")
28
+ finally:
29
+ end_time = asyncio.get_event_loop().time()
30
+ elapsed_time = end_time - start_time
31
+ logger.info(f"Time taken to get base64 audio: {elapsed_time:.2f} seconds")
32
+
33
+ return result
flet_app.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import logging
3
+ import flet as ft
4
+ from audio_utils import get_base64_audio
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ async def main(page: ft.Page):
9
+ page.title = "Simple Audio Recorder"
10
+
11
+ # Log the operating system platform
12
+ logger.info(f"Operating System: {sys.platform}")
13
+
14
+ audio_recorder = ft.AudioRecorder(
15
+ audio_encoder=ft.AudioEncoder.OPUS,
16
+ on_state_changed=lambda e: update_recording_status(e.data)
17
+ )
18
+ recording_status = ft.Text()
19
+ loading_animation = ft.ProgressRing(visible=False)
20
+ audio_player = None # Initialize audio_player as None
21
+
22
+ page.overlay.append(audio_recorder) # Only add audio_recorder to overlay initially
23
+
24
+ page.update()
25
+
26
+ def start_recording(e):
27
+ record_button.disabled = True
28
+ stop_button.disabled = True
29
+ play_button.disabled = True
30
+ loading_animation.visible = True
31
+ recording_status.value = "Preparing to record..."
32
+ page.update()
33
+
34
+ logger.info("Starting recording")
35
+ audio_recorder.start_recording("")
36
+
37
+ # The actual recording start will be handled in update_recording_status
38
+
39
+ async def stop_recording_flet(e):
40
+ record_button.disabled = False
41
+ stop_button.disabled = True
42
+ page.update()
43
+ nonlocal audio_player
44
+ result_uri = await audio_recorder.stop_recording_async()
45
+
46
+ base64_audio = await get_base64_audio(page, result_uri)
47
+
48
+ if base64_audio:
49
+ audio_player = ft.Audio(src=f"data:audio/wav;base64,{base64_audio}")
50
+ page.overlay.append(audio_player)
51
+ play_button.disabled = False
52
+ else:
53
+ logger.error(f"Failed to retrieve audio data for session {page.session_id}")
54
+
55
+ page.update()
56
+ logger.info(page.controls)
57
+
58
+ def play_recording(e):
59
+ if audio_player:
60
+ audio_player.play()
61
+
62
+ def update_recording_status(status):
63
+ if status == "recording":
64
+ stop_button.disabled = False
65
+ loading_animation.visible = False
66
+ recording_status.value = "Recording in progress..."
67
+ else:
68
+ recording_status.value = f"Recording status: {status}"
69
+ page.update()
70
+
71
+ record_button = ft.ElevatedButton("Start Recording", on_click=start_recording)
72
+ stop_button = ft.ElevatedButton("Stop Recording", on_click=stop_recording_flet, disabled=True)
73
+ play_button = ft.ElevatedButton("Play Recording", on_click=play_recording, disabled=True)
74
+
75
+ def handle_list_devices(e):
76
+ devices = audio_recorder.get_input_devices()
77
+ logger.info("Input devices: %s", devices)
78
+
79
+ def handle_has_permission(e):
80
+ try:
81
+ logger.info("HasPermission: %s", audio_recorder.has_permission())
82
+ except Exception as e:
83
+ logger.error("Error checking permission: %s", str(e))
84
+
85
+ def handle_audio_encoding_test(e):
86
+ for i in list(ft.AudioEncoder):
87
+ logger.info("%s: %s", i, audio_recorder.is_supported_encoder(i))
88
+
89
+ page.add(
90
+ ft.Text("Simple Audio Recorder", size=24),
91
+ ft.Row([record_button, stop_button, play_button, loading_animation]),
92
+ recording_status,
93
+ ft.ElevatedButton("List Devices", on_click=handle_list_devices),
94
+ ft.ElevatedButton("Test AudioEncodings", on_click=handle_audio_encoding_test),
95
+ ft.ElevatedButton("Has Permission", on_click=handle_has_permission),
96
+ )
97
+
98
+ page.update()
js_execution.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import flet as ft
2
+ import asyncio
3
+ import logging
4
+ from fastapi import FastAPI, Request
5
+ from fastapi.responses import HTMLResponse
6
+ import uuid
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class JSExecutionRouter:
11
+ def __init__(self):
12
+ self.app = None
13
+
14
+ def init_app(self, app: FastAPI):
15
+ self.app = app
16
+
17
+ def get_eval_js_html(self, js_code: str, result_id: str):
18
+ escaped_js = js_code.replace('"', '&quot;')
19
+ return f"""
20
+ <!DOCTYPE html>
21
+ <html lang="en">
22
+ <head>
23
+ <meta charset="UTF-8">
24
+ <title>Execute JavaScript</title>
25
+ </head>
26
+ <body>
27
+ <script>
28
+ async function executeAndReturn() {{
29
+ try {{
30
+ const result = await (async () => {{ {escaped_js} }})();
31
+ fetch('/js-result/{result_id}', {{
32
+ method: 'POST',
33
+ headers: {{ 'Content-Type': 'application/json' }},
34
+ body: JSON.stringify({{ result }})
35
+ }});
36
+ }} catch (error) {{
37
+ console.error('Error:', error);
38
+ fetch('/js-result/{result_id}', {{
39
+ method: 'POST',
40
+ headers: {{ 'Content-Type': 'application/json' }},
41
+ body: JSON.stringify({{ error: error.toString() }})
42
+ }});
43
+ }}
44
+ }}
45
+ executeAndReturn();
46
+ </script>
47
+ </body>
48
+ </html>
49
+ """
50
+
51
+ def create_dynamic_endpoint(self, js_code: str):
52
+ if not self.app:
53
+ raise RuntimeError("DynamicRouter not initialized with FastAPI app")
54
+
55
+ result_id = str(uuid.uuid4())
56
+ result_future = asyncio.Future()
57
+
58
+ @self.app.get(f"/eval-js/{result_id}", response_class=HTMLResponse)
59
+ async def serve_eval_js_page():
60
+ return HTMLResponse(content=self.get_eval_js_html(js_code, result_id))
61
+
62
+ @self.app.post(f"/js-result/{result_id}")
63
+ async def receive_js_result(data: dict):
64
+ if 'error' in data:
65
+ result_future.set_exception(Exception(data['error']))
66
+ else:
67
+ result_future.set_result(data['result'])
68
+ return {"status": "received"}
69
+
70
+ return f"/eval-js/{result_id}", result_future
71
+
72
+ js_execution_router = JSExecutionRouter()
73
+
74
+ # Monkey patch the Page class to add eval_js method
75
+ async def eval_js(self: ft.Page, js_code: str) -> str:
76
+ endpoint, result_future = js_execution_router.create_dynamic_endpoint(js_code)
77
+ self.launch_url(endpoint, web_window_name="hidden-iframe")
78
+ return await result_future
79
+
80
+ ft.Page.eval_js = eval_js # type: ignore
81
+
82
+ def add_js_execution_routes(app: FastAPI):
83
+ js_execution_router.init_app(app)
84
+
85
+ def mount_flet_app(app: FastAPI, route: str, flet_app):
86
+ # Mount the Flet app
87
+ app.mount("/flet-app-internal", flet_app)
88
+
89
+ @app.get(route, response_class=HTMLResponse)
90
+ async def serve_main_page(request: Request):
91
+ html_content = f"""
92
+ <!DOCTYPE html>
93
+ <html lang="en">
94
+ <head>
95
+ <meta charset="UTF-8">
96
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
97
+ <title>Audio Recorder App</title>
98
+ <style>
99
+ body, html {{
100
+ margin: 0;
101
+ padding: 0;
102
+ height: 100%;
103
+ overflow: hidden;
104
+ }}
105
+ #flet-app-frame {{
106
+ width: 100%;
107
+ height: 100%;
108
+ border: none;
109
+ position: absolute;
110
+ top: 0;
111
+ left: 0;
112
+ }}
113
+ </style>
114
+ </head>
115
+ <body>
116
+ <iframe id="flet-app-frame"></iframe>
117
+ <iframe name="hidden-iframe" style="display:none;"></iframe>
118
+ <script>
119
+ window.onload = function() {{
120
+ var iframe = document.getElementById('flet-app-frame');
121
+ var currentProtocol = window.location.protocol;
122
+ var currentHost = window.location.host;
123
+ var src = currentProtocol + '//' + currentHost + '/flet-app-internal/';
124
+
125
+ iframe.src = src;
126
+ console.log('Iframe src set to:', src);
127
+
128
+ // Set Content-Security-Policy based on the current protocol
129
+ var meta = document.createElement('meta');
130
+ meta.httpEquiv = "Content-Security-Policy";
131
+ meta.content = currentProtocol === 'https:' ? "upgrade-insecure-requests" : "";
132
+ document.head.appendChild(meta);
133
+ }};
134
+ </script>
135
+ </body>
136
+ </html>
137
+ """
138
+ return HTMLResponse(content=html_content)
139
+
140
+ # Add the JS execution routes
141
+ add_js_execution_routes(app)