awacke1 commited on
Commit
eb34529
ยท
verified ยท
1 Parent(s): 5978ca5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +185 -291
app.py CHANGED
@@ -5,7 +5,6 @@ import glob
5
  import textwrap
6
  from datetime import datetime
7
  from pathlib import Path
8
- from contextlib import redirect_stdout
9
 
10
  import streamlit as st
11
  import pandas as pd
@@ -16,98 +15,98 @@ from reportlab.lib.utils import ImageReader
16
  import mistune
17
  from gtts import gTTS
18
 
19
- # --- Core Utility Functions ---
20
 
21
- # ๐Ÿช„ Now you see it, now you don't.
22
- def delete_asset(path: str):
23
- if os.path.exists(path):
 
24
  os.remove(path)
 
 
25
  st.rerun()
26
 
27
- # ๐Ÿ•ต๏ธโ€โ™‚๏ธ On the hunt for related files in the digital jungle.
28
- def get_project_files(pattern: str = "*_*.*") -> dict:
29
- projects = {}
30
- for f in glob.glob(pattern):
31
- stem = Path(f).stem
32
- project_name = '_'.join(stem.split('_')[:-1])
33
- if project_name not in projects:
34
- projects[project_name] = {'md': [], 'images': []}
35
-
36
- ext = Path(f).suffix.lower()
37
- if ext == '.md':
38
- projects[project_name]['md'].append(f)
39
- elif ext in ['.png', '.jpg', '.jpeg']:
40
- projects[project_name]['images'].append(f)
41
- return projects
42
-
43
- # โœจ Turning markdown into its simple, unadorned soul.
44
- def md_to_plain_text(md_text: str) -> str:
45
- if not md_text:
46
- return ""
47
- html = mistune.html(md_text)
48
- return re.sub(r'<[^>]+>', '', html).strip()
49
-
50
- # --- PDF & Audio Generation ---
51
-
52
- # ๐ŸŽ™๏ธ Lending a golden voice to your silent words.
53
- def generate_audio(text: str, filename_stem: str, lang: str, slow: bool) -> str | None:
54
- if not text:
55
- st.warning("No text provided to generate audio.")
56
- return None
57
-
58
- voice_file = f"{filename_stem}.mp3"
 
 
59
  try:
60
- tts = gTTS(text=text, lang=lang, slow=slow)
61
- tts.save(voice_file)
62
- return voice_file
 
 
63
  except Exception as e:
64
  st.error(f"Failed to generate audio: {e}")
65
- return None
66
 
67
- # โœ๏ธ Weaving words into PDF poetry.
68
- def render_pdf_text(c: canvas.Canvas, text: str, settings: dict):
 
 
 
 
 
69
  page_w, page_h = letter
70
  margin = 40
71
  gutter = 20
72
- col_w = (page_w - 2 * margin - (settings['columns'] - 1) * gutter) / settings['columns']
73
-
74
- c.setFont(settings['font_family'], settings['font_size'])
75
- line_height = settings['font_size'] * 1.2
76
- wrap_width = int(col_w / (settings['font_size'] * 0.6))
77
 
78
  y = page_h - margin
79
- col = 0
80
- x = margin
81
-
82
- for paragraph in text.split("\n"):
83
- wrapped_lines = textwrap.wrap(paragraph, wrap_width, replace_whitespace=False, drop_whitespace=False)
84
- if not wrapped_lines:
85
- y -= line_height
86
- if y < margin:
87
- col, x, y = new_pdf_page_or_column(c, settings, col, margin, col_w, gutter, page_h)
88
-
89
  for line in wrapped_lines:
90
  if y < margin:
91
- col, x, y = new_pdf_page_or_column(c, settings, col, margin, col_w, gutter, page_h)
92
-
 
 
 
 
 
 
93
  c.drawString(x, y, line)
94
  y -= line_height
 
95
 
96
- # ๐Ÿ“„ Time to turn the page, or at least scoot over.
97
- def new_pdf_page_or_column(c, settings, col, margin, col_w, gutter, page_h):
98
- col += 1
99
- if col >= settings['columns']:
100
- c.showPage()
101
- c.setFont(settings['font_family'], settings['font_size'])
102
- col = 0
103
-
104
- x = margin + col * (col_w + gutter)
105
- y = page_h - margin
106
- return col, x, y
107
-
108
- # ๐Ÿ–ผ๏ธ Arranging your pixels perfectly on the page.
109
- def render_pdf_images(c: canvas.Canvas, image_files: list):
110
- for img_file in image_files:
111
  try:
112
  img = Image.open(img_file)
113
  w, h = img.size
@@ -118,254 +117,149 @@ def render_pdf_images(c: canvas.Canvas, image_files: list):
118
  st.warning(f"Could not process image {img_file.name}: {e}")
119
  continue
120
 
121
- # ๐Ÿ“œ The grand finale: text and images join forces in a PDF.
122
- def generate_pdf_from_content(text: str, images: list, settings: dict, filename_stem: str) -> bytes:
123
- buf = io.BytesIO()
124
- c = canvas.Canvas(buf, pagesize=letter)
125
-
126
- if text:
127
- render_pdf_text(c, text, settings)
128
-
129
- if images:
130
- render_pdf_images(c, images)
131
-
132
  c.save()
133
  buf.seek(0)
134
- return buf.getvalue()
135
-
136
- # --- Streamlit UI Components ---
137
-
138
- # ๐ŸŽ›๏ธ Organizing the mission control for your creative genius.
139
- def setup_sidebar(projects: dict):
140
- st.sidebar.header("๐ŸŽจ PDF Style Settings")
141
- settings = {
142
- 'columns': st.sidebar.slider("Text columns", 1, 3, 1),
143
- 'font_family': st.sidebar.selectbox("Font Family", ["Helvetica", "Times-Roman", "Courier"]),
144
- 'font_size': st.sidebar.slider("Font Size", 6, 24, 12)
145
- }
146
-
147
- st.sidebar.header("๐Ÿ“‚ Project Files")
148
- st.sidebar.caption("Files matching the `Name_Date` pattern.")
149
- if not projects:
150
- st.sidebar.info("No projects found. Upload files with a `_` in the name.")
151
- else:
152
- sorted_projects = sorted(projects.items())
153
- for name, files in sorted_projects:
154
- with st.sidebar.expander(f"Project: {name}"):
155
- if files['md']:
156
- st.write("๐Ÿ“„ " + ", ".join(Path(f).name for f in files['md']))
157
- if files['images']:
158
- st.write("๐Ÿ–ผ๏ธ " + ", ".join(Path(f).name for f in files['images']))
159
-
160
- return settings
161
-
162
- # ๐Ÿ† Putting your magnificent creations on display.
163
- def display_local_assets():
164
  st.markdown("---")
165
  st.subheader("๐Ÿ“‚ Available Assets")
166
- assets = sorted(glob.glob("*.pdf") + glob.glob("*.mp3"))
167
  if not assets:
168
- st.info("No PDFs or MP3s generated yet.")
169
  return
170
-
171
- for asset_path in assets:
172
- ext = Path(asset_path).suffix.lower()
173
- cols = st.columns([4, 2, 1])
174
- cols[0].write(f"`{asset_path}`")
175
-
176
- with open(asset_path, 'rb') as f:
177
- file_bytes = f.read()
178
-
179
- if ext == '.pdf':
180
- cols[1].download_button("โฌ‡๏ธ Download PDF", data=file_bytes, file_name=asset_path, mime="application/pdf")
181
- elif ext == '.mp3':
182
- cols[1].audio(file_bytes, format='audio/mpeg')
183
-
184
- cols[2].button("๐Ÿ—‘๏ธ Delete", key=f"del_{asset_path}", on_click=delete_asset, args=(asset_path,))
185
-
186
- # ๐ŸŽญ The main stage for our PDF and Voice show.
187
- def pdf_composer_tab(projects: dict):
188
- st.header("๐Ÿ“„ PDF Composer & Voice Generator ๐Ÿš€")
189
-
190
- col1, col2 = st.columns(2)
191
- with col1:
192
- input_method = st.radio(
193
- "Choose your content source:",
194
- ["Select an existing project", "Upload new files or paste text"],
195
- horizontal=True,
196
- label_visibility="collapsed"
197
- )
198
-
199
- md_text = ""
200
- selected_images = []
201
- filename_stem = datetime.now().strftime('%Y%m%d_%H%M%S')
202
-
203
- if input_method == "Select an existing project" and projects:
204
- sorted_project_names = sorted(projects.keys())
205
- chosen_project = st.selectbox("Select Project", sorted_project_names)
206
-
207
- md_files = projects[chosen_project]['md']
208
- if md_files:
209
- md_path = md_files[0]
210
- filename_stem = Path(md_path).stem
211
- with open(md_path, 'r', encoding='utf-8') as f:
212
- md_text = f.read()
213
- st.text_area("Markdown Content", value=md_text, height=250, key="md_from_project")
214
-
215
- image_files = projects[chosen_project]['images']
216
- if image_files:
217
- st.info(f"Found {len(image_files)} related image(s):")
218
- for img in image_files:
219
- st.image(img, width=150, caption=Path(img).name)
220
- with open(img, 'rb') as f:
221
- bytes_io = io.BytesIO(f.read())
222
- bytes_io.name = Path(img).name
223
- selected_images.append(bytes_io)
224
-
225
- else:
226
- st.info("Upload a Markdown file, or just paste your text below.")
227
- uploaded_files = st.file_uploader(
228
- "Upload Markdown (.md) and Image files (.png, .jpg)",
229
- type=["md", "png", "jpg", "jpeg"],
230
- accept_multiple_files=True
231
- )
232
-
233
- md_from_upload = [f for f in uploaded_files if f.type == "text/markdown"]
234
- images_from_upload = [f for f in uploaded_files if f.type != "text/markdown"]
235
-
236
- if md_from_upload:
237
- md_file = md_from_upload[0]
238
- md_text = md_file.getvalue().decode("utf-8")
239
- filename_stem = Path(md_file.name).stem
240
-
241
- md_text = st.text_area("Markdown Text", value=md_text, height=250, key="md_from_paste")
242
- selected_images.extend(images_from_upload)
243
 
244
- if selected_images:
245
- st.subheader("๐Ÿ–ผ๏ธ Arrange Images")
246
- st.caption("Drag rows to reorder the images for the PDF.")
247
- df_imgs = pd.DataFrame([{"order": i + 1, "name": f.name, "preview": f} for i, f in enumerate(selected_images)])
248
 
249
- edited_df = st.data_editor(
250
- df_imgs,
251
- column_config={"preview": st.column_config.ImageColumn("Preview")},
252
- hide_index=True,
253
- use_container_width=True,
254
- num_rows="dynamic"
255
- )
256
- ordered_names = edited_df['name'].tolist()
257
- selected_images.sort(key=lambda x: ordered_names.index(x.name))
 
258
 
259
- plain_text = md_to_plain_text(md_text)
260
 
261
- st.markdown("---")
262
- st.subheader("๐ŸŽฌ Generate Your Files")
263
-
264
- pdf_settings = setup_sidebar(projects)
265
-
266
- pdf_col, voice_col = st.columns(2)
267
-
268
- with pdf_col:
269
- if st.button("๐Ÿ–‹๏ธ Generate PDF", use_container_width=True, type="primary"):
270
- if not plain_text and not selected_images:
271
- st.error("Cannot generate an empty PDF. Please add some text or images.")
272
- else:
273
- with st.spinner("Crafting your PDF..."):
274
- pdf_bytes = generate_pdf_from_content(plain_text, selected_images, pdf_settings, filename_stem)
275
- st.download_button(
276
- label="โฌ‡๏ธ Download PDF",
277
- data=pdf_bytes,
278
- file_name=f"{filename_stem}.pdf",
279
- mime="application/pdf",
280
- use_container_width=True
281
- )
282
- st.success("PDF is ready for download!")
283
-
284
- with voice_col:
285
- st.markdown("<h6>Voice Generation</h6>", unsafe_allow_html=True)
286
- languages = {"English (US)": "en", "English (UK)": "en-gb", "Spanish": "es"}
287
- voice_choice = st.selectbox("Voice Language", list(languages.keys()))
288
- slow_speech = st.checkbox("Slow Speech")
289
-
290
- if st.button("๐Ÿ”Š Generate MP3", use_container_width=True):
291
- with st.spinner("Converting text to speech..."):
292
- audio_file = generate_audio(plain_text, filename_stem, languages[voice_choice], slow_speech)
293
- if audio_file:
294
- st.success("MP3 generated!")
295
- with open(audio_file, 'rb') as mp3:
296
- st.download_button("๐Ÿ“ฅ Download MP3", data=mp3, file_name=audio_file, mime="audio/mpeg", use_container_width=True)
297
- st.audio(audio_file)
298
-
299
- display_local_assets()
300
-
301
- # --- Code Interpreter Section ---
302
-
303
- # ๐Ÿ‘ป Catching code spirits in a bottle (or a buffer).
304
- def execute_code(code: str) -> tuple[str | None, str | None]:
305
- buf = io.StringIO()
306
- try:
307
- with redirect_stdout(buf):
308
- exec(code, {})
309
- return buf.getvalue(), None
310
- except Exception as e:
311
- return None, str(e)
312
 
313
- # ๐Ÿ Finding the sneaky Python hidden in the markdown grass.
314
- def extract_python_code(md: str) -> list[str]:
315
- return re.findall(r"```python\s*(.*?)```", md, re.DOTALL)
316
 
317
- # ๐Ÿงช A safe lab for your wild Python experiments.
318
- def code_interpreter_tab():
319
- st.header("๐Ÿ Python Code Executor")
320
- st.info("Execute Python code snippets. Note: This runs within the Streamlit environment.")
 
 
 
 
321
 
322
- DEFAULT_CODE = 'import streamlit as st\n\nst.balloons()\nst.write("Hello from the code interpreter!")'
323
-
324
  if 'code' not in st.session_state:
325
  st.session_state.code = DEFAULT_CODE
326
 
327
- uploaded_file = st.file_uploader("Upload .py or .md file with Python code", type=['py', 'md'])
328
-
329
  if uploaded_file:
330
- text = uploaded_file.getvalue().decode()
331
  if uploaded_file.type == 'text/markdown':
332
- codes = extract_python_code(text)
333
  st.session_state.code = codes[0] if codes else ''
334
  else:
335
- st.session_state.code = text
336
-
337
- st.session_state.code = st.text_area("Code Editor", value=st.session_state.code, height=300, key="code_editor")
 
338
 
339
- run_col, clear_col = st.columns(2)
340
- if run_col.button("โ–ถ๏ธ Run Code", use_container_width=True, type="primary"):
341
- output, error = execute_code(st.session_state.code)
342
- if error:
343
- st.error(f"Execution Failed:\n\n{error}")
344
  elif output:
345
- st.subheader("Output")
346
- st.code(output, language=None)
347
  else:
348
- st.success("โœ… Executed successfully with no output.")
349
-
350
- if clear_col.button("๐Ÿ—‘๏ธ Clear Code", use_container_width=True):
351
  st.session_state.code = ''
352
  st.rerun()
353
 
354
  # --- Main App ---
355
-
356
- # ๐ŸŽฌ Lights, camera, action! Kicking off the whole show.
357
  def main():
 
358
  st.set_page_config(page_title="PDF & Code Interpreter", layout="wide", page_icon="๐Ÿš€")
359
-
360
- project_files = get_project_files()
361
-
362
  tab1, tab2 = st.tabs(["๐Ÿ“„ PDF Composer", "๐Ÿงช Code Interpreter"])
363
-
364
  with tab1:
365
- pdf_composer_tab(project_files)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  with tab2:
368
- code_interpreter_tab()
369
 
370
  if __name__ == "__main__":
371
- main()
 
5
  import textwrap
6
  from datetime import datetime
7
  from pathlib import Path
 
8
 
9
  import streamlit as st
10
  import pandas as pd
 
15
  import mistune
16
  from gtts import gTTS
17
 
18
+ # --- Helper Functions ---
19
 
20
+ # ๐Ÿ—‘๏ธ Deletes a specified file and reruns the app.
21
+ def delete_asset(path):
22
+ """Safely deletes a file if it exists and reruns the Streamlit app."""
23
+ try:
24
  os.remove(path)
25
+ except OSError as e:
26
+ st.error(f"Error deleting file {path}: {e}")
27
  st.rerun()
28
 
29
+ # ๐Ÿ“ฅ Gets text input from either a file upload or a text area.
30
+ def get_text_input(file_uploader_label, accepted_types, text_area_label):
31
+ """
32
+ Provides UI for uploading a text file or entering text manually.
33
+ Returns the text content and a filename stem.
34
+ """
35
+ md_text = ""
36
+ stem = datetime.now().strftime('%Y%m%d_%H%M%S')
37
+
38
+ uploaded_file = st.file_uploader(file_uploader_label, type=accepted_types)
39
+ if uploaded_file:
40
+ md_text = uploaded_file.getvalue().decode("utf-8")
41
+ stem = Path(uploaded_file.name).stem
42
+ else:
43
+ md_text = st.text_area(text_area_label, height=200)
44
+
45
+ # Convert markdown to plain text for processing
46
+ renderer = mistune.HTMLRenderer()
47
+ markdown = mistune.create_markdown(renderer=renderer)
48
+ html = markdown(md_text or "")
49
+ plain_text = re.sub(r'<[^>]+>', '', html)
50
+
51
+ return plain_text, stem
52
+
53
+ # ๐Ÿ—ฃ๏ธ Generates an MP3 voice file from text using gTTS.
54
+ def generate_voice_file(text, lang, is_slow, filename):
55
+ """
56
+ Creates an MP3 from text, saves it, and provides it for playback and download.
57
+ """
58
+ if not text.strip():
59
+ st.warning("No text to generate voice from.")
60
+ return
61
+
62
+ voice_file_path = f"{filename}.mp3"
63
  try:
64
+ tts = gTTS(text=text, lang=lang, slow=is_slow)
65
+ tts.save(voice_file_path)
66
+ st.audio(voice_file_path)
67
+ with open(voice_file_path, 'rb') as fp:
68
+ st.download_button("๐Ÿ“ฅ Download MP3", data=fp, file_name=voice_file_path, mime="audio/mpeg")
69
  except Exception as e:
70
  st.error(f"Failed to generate audio: {e}")
 
71
 
72
+ # ๐Ÿ“„ Creates a PDF document from text and images.
73
+ def generate_pdf(text_content, images, pdf_params):
74
+ """
75
+ Generates a PDF buffer from text and a list of images based on specified parameters.
76
+ """
77
+ buf = io.BytesIO()
78
+ c = canvas.Canvas(buf)
79
  page_w, page_h = letter
80
  margin = 40
81
  gutter = 20
82
+ col_w = (page_w - 2 * margin - (pdf_params['columns'] - 1) * gutter) / pdf_params['columns']
83
+
84
+ c.setFont(pdf_params['font_family'], pdf_params['font_size'])
85
+ line_height = pdf_params['font_size'] * 1.2
86
+ wrap_width = int(col_w / (pdf_params['font_size'] * 0.6))
87
 
88
  y = page_h - margin
89
+ col_idx = 0
90
+
91
+ # --- Render Text ---
92
+ for paragraph in text_content.split("\n"):
93
+ wrapped_lines = textwrap.wrap(paragraph, wrap_width) if paragraph.strip() else [""]
 
 
 
 
 
94
  for line in wrapped_lines:
95
  if y < margin:
96
+ col_idx += 1
97
+ if col_idx >= pdf_params['columns']:
98
+ c.showPage()
99
+ c.setFont(pdf_params['font_family'], pdf_params['font_size'])
100
+ col_idx = 0
101
+ y = page_h - margin
102
+
103
+ x = margin + col_idx * (col_w + gutter)
104
  c.drawString(x, y, line)
105
  y -= line_height
106
+ y -= line_height # Add extra space for paragraph breaks
107
 
108
+ # --- Render Images ---
109
+ for img_file in images:
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  try:
111
  img = Image.open(img_file)
112
  w, h = img.size
 
117
  st.warning(f"Could not process image {img_file.name}: {e}")
118
  continue
119
 
 
 
 
 
 
 
 
 
 
 
 
120
  c.save()
121
  buf.seek(0)
122
+ return buf
123
+
124
+ # ๐Ÿ—‚๏ธ Displays a list of generated assets with download/delete options.
125
+ def show_asset_manager():
126
+ """Scans for local files and displays them with management controls."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  st.markdown("---")
128
  st.subheader("๐Ÿ“‚ Available Assets")
129
+ assets = sorted(glob.glob("*.*"))
130
  if not assets:
131
+ st.info("No assets generated yet.")
132
  return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
+ for asset_path in assets:
135
+ ext = asset_path.split('.')[-1].lower()
136
+ cols = st.columns([3, 1, 1])
137
+ cols[0].write(asset_path)
138
 
139
+ try:
140
+ with open(asset_path, 'rb') as fp:
141
+ file_bytes = fp.read()
142
+
143
+ if ext == 'pdf':
144
+ cols[1].download_button("๐Ÿ“ฅ", data=file_bytes, file_name=asset_path, mime="application/pdf", key=f"dl_{asset_path}")
145
+ elif ext == 'mp3':
146
+ cols[1].audio(file_bytes)
147
+ except Exception as e:
148
+ cols[1].error("Error reading file.")
149
 
150
+ cols[2].button("๐Ÿ—‘๏ธ", key=f"del_{asset_path}", on_click=delete_asset, args=(asset_path,))
151
 
152
+ # ๏ฟฝ Renders the entire UI and logic for the Python code interpreter.
153
+ def render_code_interpreter():
154
+ """Sets up the UI and execution logic for the code interpreter tab."""
155
+ st.header("๐Ÿงช Python Code Executor & Demo")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
+ # --- Nested Helper Functions for this Tab ---
158
+ def extract_python_code(md_text):
159
+ return re.findall(r"```python\s*(.*?)```", md_text, re.DOTALL)
160
 
161
+ def execute_code(code_str):
162
+ output_buffer = io.StringIO()
163
+ try:
164
+ with redirect_stdout(output_buffer):
165
+ exec(code_str, {})
166
+ return output_buffer.getvalue(), None
167
+ except Exception as e:
168
+ return None, str(e)
169
 
170
+ # --- Main Logic for the Tab ---
171
+ DEFAULT_CODE = "import streamlit as st\n\nst.balloons()\nst.write('Hello, World!')"
172
  if 'code' not in st.session_state:
173
  st.session_state.code = DEFAULT_CODE
174
 
175
+ uploaded_file = st.file_uploader("Upload .py or .md", type=['py', 'md'])
 
176
  if uploaded_file:
177
+ file_content = uploaded_file.getvalue().decode()
178
  if uploaded_file.type == 'text/markdown':
179
+ codes = extract_python_code(file_content)
180
  st.session_state.code = codes[0] if codes else ''
181
  else:
182
+ st.session_state.code = file_content
183
+ st.code(st.session_state.code, language='python')
184
+ else:
185
+ st.session_state.code = st.text_area("๐Ÿ’ป Code Editor", value=st.session_state.code, height=300)
186
 
187
+ c1, c2 = st.columns(2)
188
+ if c1.button("โ–ถ๏ธ Run Code", use_container_width=True):
189
+ output, err = execute_code(st.session_state.code)
190
+ if err:
191
+ st.error(err)
192
  elif output:
193
+ st.code(output, language='text')
 
194
  else:
195
+ st.success("Executed successfully with no output.")
196
+ if c2.button("๐Ÿ—‘๏ธ Clear Code", use_container_width=True):
 
197
  st.session_state.code = ''
198
  st.rerun()
199
 
200
  # --- Main App ---
 
 
201
  def main():
202
+ """Main function to run the Streamlit application."""
203
  st.set_page_config(page_title="PDF & Code Interpreter", layout="wide", page_icon="๐Ÿš€")
204
+
 
 
205
  tab1, tab2 = st.tabs(["๐Ÿ“„ PDF Composer", "๐Ÿงช Code Interpreter"])
206
+
207
  with tab1:
208
+ st.header("๐Ÿ“„ PDF Composer & Voice Generator ๐Ÿš€")
209
+
210
+ # --- Sidebar Controls ---
211
+ st.sidebar.title("PDF Settings")
212
+ pdf_params = {
213
+ 'columns': st.sidebar.slider("Text columns", 1, 3, 1),
214
+ 'font_family': st.sidebar.selectbox("Font", ["Helvetica", "Times-Roman", "Courier"]),
215
+ 'font_size': st.sidebar.slider("Font size", 6, 24, 12),
216
+ }
217
+
218
+ # --- Main UI ---
219
+ plain_text, filename_stem = get_text_input(
220
+ "Upload Markdown (.md)", ["md"], "Or enter markdown text directly"
221
+ )
222
+
223
+ st.subheader("๐Ÿ—ฃ๏ธ Voice Generation")
224
+ languages = {"English (US)": "en", "English (UK)": "en-uk", "Spanish": "es"}
225
+ voice_choice = st.selectbox("Voice Language", list(languages.keys()))
226
+ slow_speech = st.checkbox("Slow Speech")
227
+
228
+ if st.button("๐Ÿ”Š Generate Voice MP3"):
229
+ generate_voice_file(plain_text, languages[voice_choice], slow_speech, filename_stem)
230
+
231
+ st.subheader("๐Ÿ–ผ๏ธ Image Upload")
232
+ uploaded_images = st.file_uploader(
233
+ "Upload Images for PDF", type=["png", "jpg", "jpeg"], accept_multiple_files=True
234
+ )
235
 
236
+ ordered_images = []
237
+ if uploaded_images:
238
+ df_imgs = pd.DataFrame([{"name": f.name, "order": i} for i, f in enumerate(uploaded_images)])
239
+ edited_df = st.data_editor(df_imgs, use_container_width=True, key="img_order_editor")
240
+
241
+ # Create a map for quick lookup
242
+ image_map = {f.name: f for f in uploaded_images}
243
+
244
+ # Sort and append images based on the edited order
245
+ for _, row in edited_df.sort_values("order").iterrows():
246
+ if row['name'] in image_map:
247
+ ordered_images.append(image_map[row['name']])
248
+
249
+ st.subheader("๐Ÿ–‹๏ธ PDF Generation")
250
+ if st.button("Generate PDF with Markdown & Images"):
251
+ pdf_buffer = generate_pdf(plain_text, ordered_images, pdf_params)
252
+ st.download_button(
253
+ "โฌ‡๏ธ Download PDF",
254
+ data=pdf_buffer,
255
+ file_name=f"{filename_stem}.pdf",
256
+ mime="application/pdf"
257
+ )
258
+
259
+ show_asset_manager()
260
+
261
  with tab2:
262
+ render_code_interpreter()
263
 
264
  if __name__ == "__main__":
265
+ main()