|
import dash |
|
from dash import dcc, html, Input, Output, State, callback_context, no_update |
|
import dash_bootstrap_components as dbc |
|
import base64 |
|
import io |
|
import re |
|
import os |
|
import requests |
|
import logging |
|
from pptx import Presentation |
|
from pptx.util import Inches, Pt |
|
from concurrent.futures import ThreadPoolExecutor |
|
from dash.exceptions import PreventUpdate |
|
from PIL import Image |
|
import google.generativeai as genai |
|
import time |
|
import uuid |
|
import threading |
|
import tempfile |
|
import shutil |
|
import flask |
|
import json |
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
|
logger = logging.getLogger(__name__) |
|
|
|
STABILITY_API_KEY = os.getenv('STABILITY_API_KEY') |
|
GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY') |
|
genai.configure(api_key=GOOGLE_API_KEY) |
|
model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25') |
|
|
|
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP, 'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap']) |
|
app.config.suppress_callback_exceptions = True |
|
|
|
SESSION_DATA = {} |
|
SESSION_DATA_LOCK = threading.Lock() |
|
USER_TEMP_DIR = os.path.join(tempfile.gettempdir(), "pptx_dash_sessions") |
|
os.makedirs(USER_TEMP_DIR, exist_ok=True) |
|
|
|
def get_session_temp_dir(session_id): |
|
path = os.path.join(USER_TEMP_DIR, session_id) |
|
os.makedirs(path, exist_ok=True) |
|
return path |
|
|
|
def get_session_id_from_cookie(cookies): |
|
if not cookies: |
|
return None |
|
items = cookies.split(';') |
|
for item in items: |
|
k, sep, v = item.strip().partition('=') |
|
if k == 'dash-session-id' and sep: |
|
return v |
|
return None |
|
|
|
def save_to_session_temp_file(session_id, filename, content_bytes): |
|
session_dir = get_session_temp_dir(session_id) |
|
file_path = os.path.join(session_dir, filename) |
|
with open(file_path, "wb") as f: |
|
f.write(content_bytes) |
|
logger.info(f"Saved file '{filename}' to session dir '{session_dir}' for session '{session_id}'. (path: {file_path})") |
|
return file_path |
|
|
|
def read_session_temp_file(file_path): |
|
with open(file_path, "rb") as f: |
|
return f.read() |
|
|
|
def list_session_files(session_id): |
|
session_dir = get_session_temp_dir(session_id) |
|
files = [] |
|
for fname in os.listdir(session_dir): |
|
fpath = os.path.join(session_dir, fname) |
|
if os.path.isfile(fpath): |
|
if fname.endswith('.pptx'): |
|
ftype = 'generated_pptx' |
|
elif fname.endswith('.md'): |
|
ftype = 'markdown' |
|
else: |
|
ftype = 'uploaded' |
|
files.append({'name': fname, 'path': fpath, 'type': ftype}) |
|
logger.info(f"File list rebuilt from disk for session '{session_id}': {[f['name'] for f in files]}") |
|
return files |
|
|
|
def save_current_slide_md_to_disk(session_id, md_text): |
|
session_dir = get_session_temp_dir(session_id) |
|
md_path = os.path.join(session_dir, 'current_slide.md') |
|
with open(md_path, 'w', encoding='utf-8') as f: |
|
f.write(md_text) |
|
logger.info(f"Saved current markdown to '{md_path}' for session '{session_id}'") |
|
return md_path |
|
|
|
def load_current_slide_md_from_disk(session_id): |
|
session_dir = get_session_temp_dir(session_id) |
|
md_path = os.path.join(session_dir, 'current_slide.md') |
|
if os.path.exists(md_path): |
|
with open(md_path, 'r', encoding='utf-8') as f: |
|
data = f.read() |
|
logger.info(f"Loaded markdown from '{md_path}' for session '{session_id}'") |
|
return data |
|
return "" |
|
|
|
def get_or_create_session_data(session_id): |
|
with SESSION_DATA_LOCK: |
|
if session_id not in SESSION_DATA: |
|
session_dir = get_session_temp_dir(session_id) |
|
files_box = list_session_files(session_id) |
|
current_md = load_current_slide_md_from_disk(session_id) |
|
SESSION_DATA[session_id] = { |
|
'log_messages': [], |
|
'generated_pptx_path': None, |
|
'uploaded_files': [], |
|
'lock': threading.Lock(), |
|
'slide_markdowns': [], |
|
'slide_markdown_names': [], |
|
'current_slide_md': current_md, |
|
'files_box': files_box, |
|
} |
|
else: |
|
SESSION_DATA[session_id]['files_box'] = list_session_files(session_id) |
|
SESSION_DATA[session_id]['current_slide_md'] = load_current_slide_md_from_disk(session_id) |
|
return SESSION_DATA[session_id] |
|
|
|
def add_log(message, session_id): |
|
session = get_or_create_session_data(session_id) |
|
with session['lock']: |
|
logger.info(f"[Session {session_id}] {message}") |
|
session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - {message}") |
|
|
|
def process_document(text, session_id): |
|
add_log("Starting document processing...", session_id) |
|
prompt = f""" |
|
Create a PowerPoint presentation based on the following document. |
|
Generate slides with titles and content. Format the output as markdown, |
|
with each slide starting with '# Slide X:' where X is the slide number. |
|
Use '##' for subtitles and '-' for bullet points. Provide only the |
|
slides in markdown and nothing else, to intro, no acknowlegements, no outro, no summary, just the powerpoint. |
|
do not use any other markdown like * or ** |
|
There can only be one slide heading # and one sub heading ## and no more than 5 bullets with "-" per slide |
|
Document: |
|
{text} |
|
""" |
|
add_log("Sending request to Gemini model...", session_id) |
|
response = model.generate_content(prompt) |
|
add_log("Received response from Gemini model.", session_id) |
|
return response.text |
|
|
|
def generate_image_prompt(slide_content, session_id): |
|
add_log(f"Generating image prompt for slide: {slide_content[:30]}...", session_id) |
|
prompt = f""" |
|
Based on the following slide content, create a detailed prompt for generating |
|
an image that represents the main idea of the slide. The image should be |
|
suitable for a professional presentation. NEVER NEVER HAVE ANY FORM OF TEXT OR WORDS IN THE IMAGE. |
|
|
|
{slide_content} |
|
Image prompt: |
|
""" |
|
response = model.generate_content(prompt) |
|
add_log("Image prompt generated.", session_id) |
|
return response.text |
|
|
|
def generate_image(prompt, session_id): |
|
add_log(f"Generating image for prompt: {prompt[:30]}...", session_id) |
|
url = "https://api.stability.ai/v2beta/stable-image/generate/sd3" |
|
headers = { |
|
"Authorization": f"Bearer {STABILITY_API_KEY}", |
|
"Accept": "image/*" |
|
} |
|
data = { |
|
"prompt": prompt, |
|
"output_format": "png" |
|
} |
|
files = {"none": ''} |
|
try: |
|
response = requests.post(url, headers=headers, data=data, files=files) |
|
response.raise_for_status() |
|
add_log("Image generated successfully.", session_id) |
|
return response.content |
|
except requests.exceptions.RequestException as e: |
|
add_log(f"Error generating image: {str(e)}", session_id) |
|
return None |
|
|
|
def markdown_to_pptx(md_text, session_id): |
|
add_log("Starting PowerPoint generation...", session_id) |
|
prs = Presentation() |
|
slides = re.split(r'# Slide \d+:', md_text) |
|
slides = [slide.strip() for slide in slides if slide.strip()] |
|
with ThreadPoolExecutor() as executor: |
|
futures = [] |
|
for i, slide_content in enumerate(slides): |
|
add_log(f"Processing slide {i+1}/{len(slides)}...", session_id) |
|
image_prompt = generate_image_prompt(slide_content, session_id) |
|
futures.append(executor.submit(generate_image, image_prompt, session_id)) |
|
for i, (slide_content, future) in enumerate(zip(slides, futures)): |
|
add_log(f"Creating slide {i+1}/{len(slides)}...", session_id) |
|
lines = slide_content.split('\n') |
|
title = lines[0].strip() |
|
current_slide = prs.slides.add_slide(prs.slide_layouts[6]) |
|
title_box = current_slide.shapes.add_textbox(Inches(0.5), Inches(0.5), Inches(9), Inches(1)) |
|
title_box.text_frame.text = title |
|
title_box.text_frame.paragraphs[0].font.size = Pt(32) |
|
title_box.text_frame.paragraphs[0].font.bold = True |
|
content_box = current_slide.shapes.add_textbox(Inches(0.5), Inches(1.5), Inches(4.5), Inches(6)) |
|
text_frame = content_box.text_frame |
|
text_frame.word_wrap = True |
|
for line in lines[1:]: |
|
if line.startswith('## '): |
|
p = text_frame.add_paragraph() |
|
p.text = line[3:].strip() |
|
p.font.size = Pt(24) |
|
p.font.bold = True |
|
elif line.startswith('- '): |
|
p = text_frame.add_paragraph() |
|
p.text = line[2:].strip() |
|
p.font.size = Pt(18) |
|
p.level = 1 |
|
p.bullet = True |
|
add_log(f"Adding image to slide {i+1}...", session_id) |
|
image_data = future.result() |
|
if image_data: |
|
image_stream = io.BytesIO(image_data) |
|
try: |
|
img = Image.open(image_stream) |
|
aspect_ratio = img.width / img.height |
|
img_width = Inches(4) |
|
img_height = img_width / aspect_ratio |
|
current_slide.shapes.add_picture(image_stream, Inches(5.5), Inches(1.5), width=img_width, height=img_height) |
|
add_log(f"Image added successfully to slide {i+1}.", session_id) |
|
except Exception as e: |
|
add_log(f"Error adding image to slide {i+1}: {str(e)}", session_id) |
|
placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6)) |
|
placeholder.text_frame.text = "Image addition failed" |
|
else: |
|
add_log(f"Failed to generate image for slide {i+1}: {title}", session_id) |
|
placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6)) |
|
placeholder.text_frame.text = "Image generation failed" |
|
add_log("PowerPoint generation completed.", session_id) |
|
output = io.BytesIO() |
|
prs.save(output) |
|
return output.getvalue() |
|
|
|
app.layout = html.Div([ |
|
dcc.Store(id='session-id', storage_type='local'), |
|
dcc.Store(id='current-slide-index', storage_type='session'), |
|
dcc.Store(id='files-box-store', storage_type='session'), |
|
dcc.Store(id='slides-content-store', storage_type='local'), |
|
html.Script(''' |
|
(function() { |
|
function uuidv4() { |
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); |
|
return v.toString(16); |
|
}); |
|
} |
|
if (!document.cookie.split('; ').find(row => row.startsWith('dash-session-id='))) { |
|
var sessionid = uuidv4(); |
|
document.cookie = 'dash-session-id=' + sessionid + '; path=/; SameSite=Lax'; |
|
window.localStorage.setItem('dash-session-id', sessionid); |
|
} else { |
|
var sessionid = document.cookie.split('; ').find(row => row.startsWith('dash-session-id=')).split('=')[1]; |
|
window.localStorage.setItem('dash-session-id', sessionid); |
|
} |
|
window.dash_clientside = window.dash_clientside || {}; |
|
window.dash_clientside.sessionid = sessionid; |
|
})(); |
|
'''), |
|
dcc.Download(id="download-dynamic"), |
|
dbc.Container([ |
|
dbc.Row([ |
|
dbc.Col([ |
|
dbc.Card([ |
|
dbc.CardHeader("Upload or Paste Document", className="bg-light"), |
|
dbc.CardBody([ |
|
dbc.Textarea( |
|
id='document-text', |
|
placeholder='Paste your text or give me some instructions', |
|
style={'height': '80px', 'resize': 'vertical', 'whiteSpace': 'pre-wrap', 'wordWrap': 'break-word'}, |
|
className="mb-3" |
|
), |
|
dcc.Upload( |
|
id='upload-file', |
|
children=html.Div([ |
|
html.I(className="fas fa-cloud-upload-alt fa-3x mb-3"), |
|
html.Div("Drag and Drop or Select File") |
|
], className="text-center"), |
|
style={ |
|
'borderWidth': '2px', |
|
'borderStyle': 'dashed', |
|
'borderRadius': '5px', |
|
'padding': '20px', |
|
'transition': 'border .3s ease-in-out' |
|
}, |
|
className="mb-3" |
|
), |
|
dbc.Card([ |
|
dbc.CardHeader("All Uploaded/Generated Files", className="bg-light"), |
|
dbc.CardBody(id="files-box", className="mb-2"), |
|
], className="mb-3 shadow-sm"), |
|
dbc.Button("Generate Content", id='generate-content-button', color="primary", className="w-100 mt-2", size="lg"), |
|
html.Div([ |
|
html.Div("Status / Log:", className="fw-bold mb-1"), |
|
html.Div(id='log-output', style={'whiteSpace': 'pre-line', 'height': '150px', 'overflowY': 'scroll', 'border': '1px solid #ddd', 'padding': '10px'}) |
|
], className="mt-3"), |
|
]) |
|
], className="mb-4 shadow-sm") |
|
], width=4, xs=12, sm=12, md=4, lg=4, xl=4, xxl=4), |
|
dbc.Col([ |
|
dbc.Card([ |
|
dbc.CardHeader("Edit Slide Content", className="bg-light"), |
|
dbc.CardBody([ |
|
dcc.Loading( |
|
id="loading", |
|
type="default", |
|
children=[ |
|
dbc.Textarea( |
|
id='slides-content', |
|
placeholder='Generated or pasted markdown will appear here. You can edit the content.', |
|
style={'height': '400px', 'resize': 'vertical', 'whiteSpace': 'pre-wrap', 'wordWrap': 'break-word'}, |
|
className="mb-3" |
|
), |
|
dbc.Button("Generate PowerPoint", id='generate-ppt-button', color="primary", className="w-100 mt-3", size="lg"), |
|
html.Div(id="loading-placeholder", style={'height': '40px'}) |
|
] |
|
) |
|
]) |
|
], className="mb-4 shadow-sm") |
|
], width=8, xs=12, sm=12, md=8, lg=8, xl=8, xxl=8) |
|
]) |
|
], fluid=True, className="mt-4") |
|
]) |
|
|
|
@app.callback( |
|
Output('session-id', 'data'), |
|
Input('session-id', 'data') |
|
) |
|
def store_session_id(session_id): |
|
cookies = flask.request.cookies |
|
c_session_id = cookies.get('dash-session-id') |
|
if c_session_id: |
|
logger.info(f"Session ID from cookie: {c_session_id}") |
|
return c_session_id |
|
if not session_id: |
|
session_id = str(uuid.uuid4()) |
|
logger.info(f"Generated new session ID: {session_id}") |
|
return session_id |
|
|
|
@app.callback( |
|
Output('files-box', 'children'), |
|
Output('slides-content', 'value'), |
|
Output('log-output', 'children'), |
|
Output('current-slide-index', 'data'), |
|
Output("download-dynamic", "data"), |
|
Output('slides-content-store', 'data'), |
|
Input('upload-file', 'contents'), |
|
Input('generate-content-button', 'n_clicks'), |
|
Input('generate-ppt-button', 'n_clicks'), |
|
Input({'type': 'file-download', 'filename': dash.ALL}, 'n_clicks'), |
|
Input({'type': 'file-delete', 'filename': dash.ALL}, 'n_clicks'), |
|
Input('session-id', 'data'), |
|
State('upload-file', 'filename'), |
|
State('document-text', 'value'), |
|
State('current-slide-index', 'data'), |
|
State({'type': 'file-download', 'filename': dash.ALL}, 'id'), |
|
State({'type': 'file-delete', 'filename': dash.ALL}, 'id'), |
|
State('slides-content-store', 'data'), |
|
State('slides-content', 'value'), |
|
prevent_initial_call=False |
|
) |
|
def unified_callback( |
|
upload_contents, gen_content_btn, gen_ppt_btn, file_downloads, file_deletes, session_id, |
|
upload_filename, document_text, current_slide_idx, download_ids, delete_ids, slides_content_store_data, slides_content_input |
|
): |
|
ctx = callback_context |
|
if not ctx.triggered or not session_id: |
|
session = get_or_create_session_data(session_id) |
|
session['files_box'] = list_session_files(session_id) |
|
session['current_slide_md'] = load_current_slide_md_from_disk(session_id) |
|
slides_content = session.get('current_slide_md', "") |
|
return ( |
|
build_files_box(session), |
|
slides_content, |
|
'\n'.join(session['log_messages'][:100]), |
|
current_slide_idx, |
|
None, |
|
slides_content |
|
) |
|
|
|
triggered = ctx.triggered[0] |
|
prop_id = triggered['prop_id'] |
|
triggered_id = triggered.get('id', None) |
|
|
|
session = get_or_create_session_data(session_id) |
|
slide_idx = current_slide_idx if current_slide_idx is not None else None |
|
log_str = "" |
|
download_data = None |
|
|
|
slides_content = session.get('current_slide_md', "") |
|
|
|
session['files_box'] = list_session_files(session_id) |
|
|
|
def build_files_box(session): |
|
files_box = [] |
|
for f in session['files_box']: |
|
files_box.append( |
|
html.Div([ |
|
html.Div( |
|
html.A( |
|
f['name'], |
|
href="#", |
|
id={'type': 'file-download', 'filename': f['name']}, |
|
n_clicks=0, |
|
style={ |
|
'color': '#116F70', |
|
'textDecoration': 'underline', |
|
'display': 'inline-block', |
|
'overflow': 'hidden', |
|
'textOverflow': 'ellipsis', |
|
'whiteSpace': 'nowrap', |
|
'maxWidth': '140px', |
|
'verticalAlign': 'middle' |
|
}, |
|
title=f['name'] |
|
), |
|
style={ |
|
'flex': '1 1 auto', |
|
'minWidth': '0', |
|
'overflow': 'hidden' |
|
} |
|
), |
|
dbc.Button( |
|
"Delete", |
|
id={'type': 'file-delete', 'filename': f['name']}, |
|
size="sm", |
|
color="secondary", |
|
style={'marginLeft': '8px', 'marginBottom': '2px', 'flex': 'none'} |
|
) |
|
], style={'display': 'flex', 'alignItems': 'center', 'marginBottom': '8px', 'width': '100%', 'gap': '0.2em'}, id={'type': 'file-row', 'filename': f['name']}) |
|
) |
|
if not files_box: |
|
return html.Div("No files uploaded or generated yet.", className="text-muted") |
|
return files_box |
|
|
|
with session['lock']: |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
|
|
delete_triggered = False |
|
delete_filename = None |
|
if 'file-delete' in prop_id: |
|
if isinstance(delete_ids, list): |
|
for n, n_clicks in enumerate(file_deletes): |
|
if n_clicks and n_clicks > 0 and isinstance(delete_ids[n], dict): |
|
delete_triggered = True |
|
delete_filename = delete_ids[n].get('filename') |
|
break |
|
download_triggered = False |
|
download_filename = None |
|
if 'file-download' in prop_id: |
|
if isinstance(download_ids, list): |
|
for n, n_clicks in enumerate(file_downloads): |
|
if n_clicks and n_clicks > 0 and isinstance(download_ids[n], dict): |
|
download_triggered = True |
|
download_filename = download_ids[n].get('filename') |
|
break |
|
|
|
if delete_triggered and delete_filename: |
|
file_entry = next((f for f in session['files_box'] if f['name'] == delete_filename), None) |
|
if file_entry: |
|
try: |
|
os.remove(file_entry['path']) |
|
add_log(f"Deleted file '{file_entry['name']}' from '{file_entry['path']}'", session_id) |
|
logger.info(f"Deleted file '{file_entry['name']}' at '{file_entry['path']}' for session '{session_id}'") |
|
except Exception as ex: |
|
add_log(f"Failed to delete file '{file_entry['name']}' from '{file_entry['path']}': {str(ex)}", session_id) |
|
session['files_box'] = list_session_files(session_id) |
|
session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - File '{file_entry['name']}' deleted.") |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
return build_files_box(session), no_update, log_str, slide_idx, None, slides_content_input |
|
|
|
if download_triggered and download_filename: |
|
file_entry = next((f for f in session['files_box'] if f['name'] == download_filename), None) |
|
if file_entry: |
|
file_path = file_entry['path'] |
|
file_name = file_entry['name'] |
|
file_bytes = read_session_temp_file(file_path) |
|
add_log(f"Download link generated for file '{file_name}' at '{file_path}'", session_id) |
|
logger.info(f"Download triggered for file '{file_name}' at '{file_path}' session '{session_id}'") |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
return build_files_box(session), no_update, log_str, slide_idx, dcc.send_bytes(file_bytes, file_name), slides_content_input |
|
|
|
if prop_id.startswith('upload-file') and upload_contents and upload_filename: |
|
try: |
|
header, content_string = upload_contents.split(',') |
|
file_bytes = base64.b64decode(content_string) |
|
file_path = save_to_session_temp_file(session_id, upload_filename, file_bytes) |
|
session['files_box'] = list_session_files(session_id) |
|
session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Uploaded file '{upload_filename}' saved to '{file_path}' for session '{session_id}'.") |
|
logger.info(f"File '{upload_filename}' registered in session file box for '{session_id}', path: {file_path}") |
|
except Exception as e: |
|
session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Error uploading file: {str(e)}") |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
return build_files_box(session), no_update, log_str, slide_idx, None, slides_content_input |
|
|
|
elif prop_id.startswith('generate-content-button'): |
|
decoded_file_text = None |
|
uploaded_files = [f for f in session['files_box'] if f['type'] == 'uploaded'] |
|
if uploaded_files: |
|
try: |
|
decoded_file_text = read_session_temp_file(uploaded_files[0]['path']).decode('utf-8') |
|
except UnicodeDecodeError: |
|
try: |
|
decoded_file_text = read_session_temp_file(uploaded_files[0]['path']).decode('latin-1') |
|
except Exception: |
|
session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Could not decode uploaded file as text. Please upload a UTF-8 or Latin-1 encoded text file.") |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
return build_files_box(session), "Could not decode uploaded file as text. Please upload a UTF-8 or Latin-1 encoded text file.", log_str, slide_idx, None, slides_content_input |
|
|
|
combined_text = "" |
|
if decoded_file_text and document_text: |
|
combined_text = decoded_file_text.strip() + "\n\n" + document_text.strip() |
|
elif decoded_file_text: |
|
combined_text = decoded_file_text.strip() |
|
elif document_text: |
|
combined_text = document_text.strip() |
|
else: |
|
session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - Please upload a file or enter text.") |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
return build_files_box(session), "Please upload a file or enter text.", log_str, slide_idx, None, slides_content_input |
|
try: |
|
add_log("Starting slide generation...", session_id) |
|
slides_markdown = process_document(combined_text, session_id) |
|
add_log("Slide generation completed.", session_id) |
|
slide_name = f"Slides {time.strftime('%Y-%m-%d %H:%M:%S')}" |
|
session['slide_markdowns'].append(slides_markdown) |
|
session['slide_markdown_names'].append(slide_name) |
|
slides_content = slides_markdown |
|
session['current_slide_md'] = slides_markdown |
|
save_current_slide_md_to_disk(session_id, slides_markdown) |
|
slide_idx = len(session['slide_markdowns']) - 1 |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
except Exception as e: |
|
add_log(f"An error occurred during slide generation: {str(e)}", session_id) |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
return build_files_box(session), f"An error occurred: {str(e)}", log_str, slide_idx, None, slides_content_input |
|
return build_files_box(session), slides_content, log_str, slide_idx, None, slides_content |
|
|
|
elif prop_id.startswith('generate-ppt-button'): |
|
if not slides_content_input or not session_id: |
|
raise PreventUpdate |
|
pptx_bytes = None |
|
try: |
|
add_log("Starting PowerPoint generation...", session_id) |
|
pptx_bytes = markdown_to_pptx(slides_content_input, session_id) |
|
pptx_filename = f"presentation_{int(time.time())}.pptx" |
|
pptx_path = save_to_session_temp_file(session_id, pptx_filename, pptx_bytes) |
|
logger.info(f"PowerPoint saved on disk at '{pptx_path}' for session '{session_id}'") |
|
session['files_box'] = [f for f in session['files_box'] if f['type'] != 'generated_pptx'] |
|
session['files_box'].append({'name': pptx_filename, 'path': pptx_path, 'type': 'generated_pptx'}) |
|
session['generated_pptx_path'] = pptx_path |
|
session['files_box'] = list_session_files(session_id) |
|
add_log(f"PowerPoint file '{pptx_filename}' saved to '{pptx_path}' and file link generated.", session_id) |
|
logger.info(f"PowerPoint link registered for session '{session_id}': {pptx_path}") |
|
add_log("PowerPoint generation completed.", session_id) |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
session['current_slide_md'] = slides_content_input |
|
save_current_slide_md_to_disk(session_id, slides_content_input) |
|
slides_content = slides_content_input |
|
except Exception as e: |
|
add_log(f"An error occurred during PowerPoint generation: {str(e)}", session_id) |
|
log_str = '\n'.join(session['log_messages'][:100]) |
|
return build_files_box(session), no_update, log_str, slide_idx, None, slides_content_input |
|
return build_files_box(session), no_update, log_str, slide_idx, None, slides_content_input |
|
|
|
else: |
|
session['files_box'] = list_session_files(session_id) |
|
session['current_slide_md'] = load_current_slide_md_from_disk(session_id) |
|
slides_content = session.get('current_slide_md', "") |
|
return ( |
|
build_files_box(session), |
|
no_update, |
|
'\n'.join(session['log_messages'][:100]), |
|
slide_idx, |
|
None, |
|
slides_content_input |
|
) |
|
|
|
if __name__ == '__main__': |
|
print("Starting the Dash application...") |
|
app.run(debug=True, host='0.0.0.0', port=7860, threaded=True) |
|
print("Dash application has finished running.") |