File size: 11,431 Bytes
7eca515
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
import io
import os
import re
import glob
import textwrap
from datetime import datetime
from pathlib import Path

import streamlit as st
import pandas as pd
from PIL import Image
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.utils import ImageReader
import mistune
from gtts import gTTS

# Page config
st.set_page_config(page_title="PDF & Code Interpreter", layout="wide", page_icon="πŸš€")

def delete_asset(path):
    try:
        os.remove(path)
    except Exception as e:
        st.error(f"Error deleting file: {e}")
    st.rerun()

# Tabs setup
tab1, tab2 = st.tabs(["πŸ“„ PDF Composer", "πŸ§ͺ Code Interpreter"])

with tab1:
    st.header("πŸ“„ PDF Composer & Voice Generator πŸš€")

    # Sidebar PDF text settings
    columns = st.sidebar.slider("Text columns", 1, 3, 1)
    font_family = st.sidebar.selectbox("Font", ["Helvetica","Times-Roman","Courier"])
    font_size = st.sidebar.slider("Font size", 6, 24, 12)

    # Markdown input
    md_file = st.file_uploader("Upload Markdown (.md)", type=["md"])
    if md_file:
        md_text = md_file.getvalue().decode("utf-8")
        stem = Path(md_file.name).stem
    else:
        md_text = st.text_area("Or enter markdown text directly", height=200)
        stem = datetime.now().strftime('%Y%m%d_%H%M%S')

    # Convert Markdown to plain text
    # Using mistune to parse markdown to HTML, then stripping HTML tags
    renderer = mistune.HTMLRenderer()
    markdown = mistune.create_markdown(renderer=renderer)
    html = markdown(md_text or "")
    plain_text = re.sub(r'<[^>]+>', '', html) # Strip HTML tags

    # Voice settings
    languages = {"English (US)": "en", "English (UK)": "en-uk", "Spanish": "es"}
    voice_choice = st.selectbox("Voice Language", list(languages.keys()))
    voice_lang = languages[voice_choice]
    slow = st.checkbox("Slow Speech")

    if st.button("πŸ”Š Generate & Download Voice MP3 from Text"):
        if plain_text.strip():
            voice_file = f"{stem}.mp3"
            try:
                tts = gTTS(text=plain_text, lang=voice_lang, slow=slow)
                tts.save(voice_file)
                st.audio(voice_file)
                with open(voice_file, 'rb') as mp3:
                    st.download_button("πŸ“₯ Download MP3", data=mp3, file_name=voice_file, mime="audio/mpeg")
            except Exception as e:
                st.error(f"Error generating voice: {e}")
        else:
            st.warning("No text to generate voice from.")

    # Image uploads and ordering
    imgs = st.file_uploader("Upload Images for PDF", type=["png", "jpg", "jpeg"], accept_multiple_files=True)
    ordered_images = []
    if imgs:
        # Create a DataFrame for editing image order
        df_imgs = pd.DataFrame([{"name": f.name, "order": i} for i, f in enumerate(imgs)])
        edited = st.data_editor(df_imgs, use_container_width=True, num_rows="dynamic") # Use num_rows="dynamic" for better UI
        # Reconstruct the ordered list of file objects
        for _, row in edited.sort_values("order").iterrows():
            for f in imgs:
                if f.name == row['name']:
                    ordered_images.append(f)
                    break # Found the file object, move to the next row

    if st.button("πŸ–‹οΈ Generate PDF with Markdown & Images"):
        if not plain_text.strip() and not ordered_images:
             st.warning("Please provide some text or upload images to generate a PDF.")
        else:
            buf = io.BytesIO()
            c = canvas.Canvas(buf)

            # Render text with columns if there is text
            if plain_text.strip():
                page_w, page_h = letter
                margin = 40
                gutter = 20
                col_w = (page_w - 2*margin - (columns-1)*gutter) / columns
                c.setFont(font_family, font_size)
                line_height = font_size * 1.2
                col = 0
                x = margin
                y = page_h - margin
                # Estimate wrap width based on font size and column width
                # This is an approximation and might need fine-tuning
                avg_char_width = font_size * 0.6 # Approximation
                wrap_width = int(col_w / avg_char_width) if avg_char_width > 0 else 100 # Prevent division by zero

                for paragraph in plain_text.split("\n"):
                    # Handle empty lines by adding vertical space
                    if not paragraph.strip():
                        y -= line_height
                        if y < margin:
                            col += 1
                            if col >= columns:
                                c.showPage()
                                c.setFont(font_family, font_size)
                                col = 0
                            x = margin + col*(col_w+gutter)
                            y = page_h - margin
                        continue # Skip to next paragraph

                    for line in textwrap.wrap(paragraph, wrap_width):
                        if y < margin:
                            col += 1
                            if col >= columns:
                                c.showPage()
                                c.setFont(font_family, font_size)
                                col = 0
                            x = margin + col*(col_w+gutter)
                            y = page_h - margin
                        c.drawString(x, y, line)
                        y -= line_height
                    y -= line_height # Add space between paragraphs

            # Autosize pages to each image
            for img_f in ordered_images:
                try:
                    img = Image.open(img_f)
                    w, h = img.size
                    c.showPage() # Start a new page for each image
                    # Set page size to image size
                    c.setPageSize((w, h))
                    # Draw image filling the page
                    c.drawImage(ImageReader(img), 0, 0, w, h, preserveAspectRatio=False) # Use False to fill page exactly
                except Exception as e:
                    st.warning(f"Could not process image {img_f.name}: {e}")
                    continue

            c.save()
            buf.seek(0)
            pdf_name = f"{stem}.pdf"
            st.download_button("⬇️ Download PDF", data=buf, file_name=pdf_name, mime="application/pdf")

    st.markdown("---")
    st.subheader("πŸ“‚ Available Assets")
    # Get all files and filter out unwanted ones
    all_assets = glob.glob("*.*")
    excluded_extensions = ['.py', '.ttf']
    excluded_files = ['README.md', 'index.html']

    assets = sorted([
        a for a in all_assets
        if not (a.lower().endswith(tuple(excluded_extensions)) or a in excluded_files)
    ])

    if not assets:
        st.info("No available assets found.")
    else:
        for a in assets:
            ext = a.split('.')[-1].lower()
            cols = st.columns([3, 1, 1])
            cols[0].write(a)

            # Provide download/preview based on file type
            try:
                if ext == 'pdf':
                    with open(a, 'rb') as fp:
                        cols[1].download_button("πŸ“₯", data=fp, file_name=a, mime="application/pdf")
                elif ext == 'mp3':
                    # Streamlit can play audio directly from file path
                    cols[1].audio(a)
                    with open(a, 'rb') as mp3:
                         cols[1].download_button("πŸ“₯", data=mp3, file_name=a, mime="audio/mpeg")
                # Add more file types here if needed (e.g., images)
                elif ext in ['png', 'jpg', 'jpeg', 'gif']:
                     # Can't preview image directly in this column, offer download
                     with open(a, 'rb') as img_file:
                         cols[1].download_button("⬇️", data=img_file, file_name=a, mime=f"image/{ext}")
                # Handle other file types - maybe just offer download
                else:
                     with open(a, 'rb') as other_file:
                         cols[1].download_button("⬇️", data=other_file, file_name=a) # Mime type is guessed by streamlit

                # Delete button
                cols[2].button("πŸ—‘οΈ", key=f"del_{a}", on_click=delete_asset, args=(a,))
            except Exception as e:
                cols[2].error(f"Error handling file {a}: {e}")


with tab2:
    st.header("πŸ§ͺ Python Code Executor & Demo")
    import io, sys
    from contextlib import redirect_stdout

    DEFAULT_CODE = '''import streamlit as st
import random

st.title("πŸ“Š Demo App")
st.markdown("Random number and color demo")

col1, col2 = st.columns(2)
with col1:
    num = st.number_input("Number:", 1, 100, 10)
    mul = st.slider("Multiplier:", 1, 10, 2)
    if st.button("Calc"):
        st.write(num * mul)
with col2:
    color = st.color_picker("Pick color","#ff0000")
    st.markdown(f'<div style="background:{color};padding:10px;">Color</div>', unsafe_allow_html=True)
''' # noqa

    def extract_python_code(md: str) -> list:
        # Find all blocks starting with ```python and ending with ```
        return re.findall(r"```python\s*(.*?)```", md, re.DOTALL)

    def execute_code(code: str) -> tuple:
        buf = io.StringIO(); local_vars = {}
        # Redirect stdout to capture print statements
        try:
            with redirect_stdout(buf):
                # Use exec to run the code. locals() and globals() are needed.
                # Passing empty dicts might limit some functionalities but provides isolation.
                exec(code, {}, local_vars)
            return buf.getvalue(), None # Return captured output
        except Exception as e:
            return None, str(e) # Return error message

    up = st.file_uploader("Upload .py or .md", type=['py', 'md'])
    # Initialize session state for code if it doesn't exist
    if 'code' not in st.session_state:
        st.session_state.code = DEFAULT_CODE

    if up:
        text = up.getvalue().decode()
        if up.type == 'text/markdown':
            codes = extract_python_code(text)
            if codes:
                 # Take the first python code block found
                st.session_state.code = codes[0].strip()
            else:
                st.warning("No Python code block found in the markdown file.")
                st.session_state.code = '' # Clear code if no block found
        else: # .py file
            st.session_state.code = text.strip()

        # Display the code after upload
        st.code(st.session_state.code, language='python')
    else:
        # Text area for code editing if no file is uploaded or after processing upload
        st.session_state.code = st.text_area("πŸ’» Code Editor", value=st.session_state.code, height=400) # Increased height

    c1, c2 = st.columns([1, 1])
    if c1.button("▢️ Run Code"):
        if st.session_state.code.strip():
            out, err = execute_code(st.session_state.code)
            if err:
                st.error(f"Execution Error:\n{err}")
            elif out:
                st.subheader("Output:")
                st.code(out)
            else:
                st.success("Executed with no standard output.")
        else:
            st.warning("No code to run.")

    if c2.button("πŸ—‘οΈ Clear Code"):
        st.session_state.code = ''
        st.rerun()