File size: 9,581 Bytes
21cd4b9
 
 
 
2ef4458
21cd4b9
 
 
 
 
 
 
2ef4458
21cd4b9
 
 
 
5ba8d4b
21cd4b9
5ba8d4b
d0b625e
5ba8d4b
d0b625e
 
5ba8d4b
 
d0b625e
 
5ba8d4b
 
 
 
 
 
 
 
21cd4b9
5ba8d4b
 
 
 
21cd4b9
5ba8d4b
d0b625e
5ba8d4b
d0b625e
 
 
 
 
5ba8d4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ef4458
5ba8d4b
 
 
 
 
 
 
 
 
 
 
 
 
2ef4458
5ba8d4b
 
 
d0b625e
5ba8d4b
 
 
d0b625e
 
 
5ba8d4b
 
 
 
 
 
d0b625e
5ba8d4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d0b625e
5ba8d4b
 
 
 
 
 
 
21cd4b9
5ba8d4b
 
 
d0b625e
 
 
5ba8d4b
 
d0b625e
 
5ba8d4b
 
 
 
 
 
d0b625e
 
5ba8d4b
d0b625e
21cd4b9
d0b625e
 
5ba8d4b
 
 
d0b625e
 
5ba8d4b
 
d0b625e
5ba8d4b
 
d0b625e
2ef4458
5ba8d4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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

# --- Helper Functions ---

# πŸ—‘οΈ Deletes a specified file and reruns the app.
def delete_asset(path):
    """Safely deletes a file if it exists and reruns the Streamlit app."""
    try:
        os.remove(path)
    except OSError as e:
        st.error(f"Error deleting file {path}: {e}")
    st.rerun()

# πŸ“₯ Gets text input from either a file upload or a text area.
def get_text_input(file_uploader_label, accepted_types, text_area_label):
    """
    Provides UI for uploading a text file or entering text manually.
    Returns the text content and a filename stem.
    """
    md_text = ""
    stem = datetime.now().strftime('%Y%m%d_%H%M%S')

    uploaded_file = st.file_uploader(file_uploader_label, type=accepted_types)
    if uploaded_file:
        md_text = uploaded_file.getvalue().decode("utf-8")
        stem = Path(uploaded_file.name).stem
    else:
        md_text = st.text_area(text_area_label, height=200)

    # Convert markdown to plain text for processing
    renderer = mistune.HTMLRenderer()
    markdown = mistune.create_markdown(renderer=renderer)
    html = markdown(md_text or "")
    plain_text = re.sub(r'<[^>]+>', '', html)

    return plain_text, stem

# πŸ—£οΈ Generates an MP3 voice file from text using gTTS.
def generate_voice_file(text, lang, is_slow, filename):
    """
    Creates an MP3 from text, saves it, and provides it for playback and download.
    """
    if not text.strip():
        st.warning("No text to generate voice from.")
        return

    voice_file_path = f"{filename}.mp3"
    try:
        tts = gTTS(text=text, lang=lang, slow=is_slow)
        tts.save(voice_file_path)
        st.audio(voice_file_path)
        with open(voice_file_path, 'rb') as fp:
            st.download_button("πŸ“₯ Download MP3", data=fp, file_name=voice_file_path, mime="audio/mpeg")
    except Exception as e:
        st.error(f"Failed to generate audio: {e}")

# πŸ“„ Creates a PDF document from text and images.
def generate_pdf(text_content, images, pdf_params):
    """
    Generates a PDF buffer from text and a list of images based on specified parameters.
    """
    buf = io.BytesIO()
    c = canvas.Canvas(buf)
    page_w, page_h = letter
    margin = 40
    gutter = 20
    col_w = (page_w - 2 * margin - (pdf_params['columns'] - 1) * gutter) / pdf_params['columns']

    c.setFont(pdf_params['font_family'], pdf_params['font_size'])
    line_height = pdf_params['font_size'] * 1.2
    wrap_width = int(col_w / (pdf_params['font_size'] * 0.6))

    y = page_h - margin
    col_idx = 0

    # --- Render Text ---
    for paragraph in text_content.split("\n"):
        wrapped_lines = textwrap.wrap(paragraph, wrap_width) if paragraph.strip() else [""]
        for line in wrapped_lines:
            if y < margin:
                col_idx += 1
                if col_idx >= pdf_params['columns']:
                    c.showPage()
                    c.setFont(pdf_params['font_family'], pdf_params['font_size'])
                    col_idx = 0
                y = page_h - margin
            
            x = margin + col_idx * (col_w + gutter)
            c.drawString(x, y, line)
            y -= line_height
        y -= line_height # Add extra space for paragraph breaks

    # --- Render Images ---
    for img_file in images:
        try:
            img = Image.open(img_file)
            w, h = img.size
            c.showPage()
            c.setPageSize((w, h))
            c.drawImage(ImageReader(img), 0, 0, w, h, preserveAspectRatio=True, mask='auto')
        except Exception as e:
            st.warning(f"Could not process image {img_file.name}: {e}")
            continue

    c.save()
    buf.seek(0)
    return buf

# πŸ—‚οΈ Displays a list of generated assets with download/delete options.
def show_asset_manager():
    """Scans for local files and displays them with management controls."""
    st.markdown("---")
    st.subheader("πŸ“‚ Available Assets")
    assets = sorted(glob.glob("*.*"))
    if not assets:
        st.info("No assets generated yet.")
        return

    for asset_path in assets:
        ext = asset_path.split('.')[-1].lower()
        cols = st.columns([3, 1, 1])
        cols[0].write(asset_path)
        
        try:
            with open(asset_path, 'rb') as fp:
                file_bytes = fp.read()
            
            if ext == 'pdf':
                cols[1].download_button("πŸ“₯", data=file_bytes, file_name=asset_path, mime="application/pdf", key=f"dl_{asset_path}")
            elif ext == 'mp3':
                cols[1].audio(file_bytes)
        except Exception as e:
            cols[1].error("Error reading file.")

        cols[2].button("πŸ—‘οΈ", key=f"del_{asset_path}", on_click=delete_asset, args=(asset_path,))

# οΏ½ Renders the entire UI and logic for the Python code interpreter.
def render_code_interpreter():
    """Sets up the UI and execution logic for the code interpreter tab."""
    st.header("πŸ§ͺ Python Code Executor & Demo")

    # --- Nested Helper Functions for this Tab ---
    def extract_python_code(md_text):
        return re.findall(r"```python\s*(.*?)```", md_text, re.DOTALL)

    def execute_code(code_str):
        output_buffer = io.StringIO()
        try:
            with redirect_stdout(output_buffer):
                exec(code_str, {})
            return output_buffer.getvalue(), None
        except Exception as e:
            return None, str(e)

    # --- Main Logic for the Tab ---
    DEFAULT_CODE = "import streamlit as st\n\nst.balloons()\nst.write('Hello, World!')"
    if 'code' not in st.session_state:
        st.session_state.code = DEFAULT_CODE

    uploaded_file = st.file_uploader("Upload .py or .md", type=['py', 'md'])
    if uploaded_file:
        file_content = uploaded_file.getvalue().decode()
        if uploaded_file.type == 'text/markdown':
            codes = extract_python_code(file_content)
            st.session_state.code = codes[0] if codes else ''
        else:
            st.session_state.code = file_content
        st.code(st.session_state.code, language='python')
    else:
        st.session_state.code = st.text_area("πŸ’» Code Editor", value=st.session_state.code, height=300)

    c1, c2 = st.columns(2)
    if c1.button("▢️ Run Code", use_container_width=True):
        output, err = execute_code(st.session_state.code)
        if err:
            st.error(err)
        elif output:
            st.code(output, language='text')
        else:
            st.success("Executed successfully with no output.")
    if c2.button("πŸ—‘οΈ Clear Code", use_container_width=True):
        st.session_state.code = ''
        st.rerun()

# --- Main App ---
def main():
    """Main function to run the Streamlit application."""
    st.set_page_config(page_title="PDF & Code Interpreter", layout="wide", page_icon="πŸš€")

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

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

        # --- Sidebar Controls ---
        st.sidebar.title("PDF Settings")
        pdf_params = {
            '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),
        }

        # --- Main UI ---
        plain_text, filename_stem = get_text_input(
            "Upload Markdown (.md)", ["md"], "Or enter markdown text directly"
        )

        st.subheader("πŸ—£οΈ Voice Generation")
        languages = {"English (US)": "en", "English (UK)": "en-uk", "Spanish": "es"}
        voice_choice = st.selectbox("Voice Language", list(languages.keys()))
        slow_speech = st.checkbox("Slow Speech")

        if st.button("πŸ”Š Generate Voice MP3"):
            generate_voice_file(plain_text, languages[voice_choice], slow_speech, filename_stem)

        st.subheader("πŸ–ΌοΈ Image Upload")
        uploaded_images = st.file_uploader(
            "Upload Images for PDF", type=["png", "jpg", "jpeg"], accept_multiple_files=True
        )
        
        ordered_images = []
        if uploaded_images:
            df_imgs = pd.DataFrame([{"name": f.name, "order": i} for i, f in enumerate(uploaded_images)])
            edited_df = st.data_editor(df_imgs, use_container_width=True, key="img_order_editor")
            
            # Create a map for quick lookup
            image_map = {f.name: f for f in uploaded_images}
            
            # Sort and append images based on the edited order
            for _, row in edited_df.sort_values("order").iterrows():
                if row['name'] in image_map:
                    ordered_images.append(image_map[row['name']])

        st.subheader("πŸ–‹οΈ PDF Generation")
        if st.button("Generate PDF with Markdown & Images"):
            pdf_buffer = generate_pdf(plain_text, ordered_images, pdf_params)
            st.download_button(
                "⬇️ Download PDF",
                data=pdf_buffer,
                file_name=f"{filename_stem}.pdf",
                mime="application/pdf"
            )
        
        show_asset_manager()

    with tab2:
        render_code_interpreter()

if __name__ == "__main__":
    main()