bluenevus commited on
Commit
23d36a2
·
1 Parent(s): 1af7f6e

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +130 -53
app.py CHANGED
@@ -14,6 +14,8 @@ from dash.exceptions import PreventUpdate
14
  from PIL import Image
15
  import google.generativeai as genai
16
  import time
 
 
17
 
18
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
19
  logger = logging.getLogger(__name__)
@@ -26,10 +28,54 @@ model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25')
26
  app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP, 'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap'])
27
  app.config.suppress_callback_exceptions = True
28
 
29
- log_messages = []
30
- generated_pptx = None
 
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  app.layout = html.Div([
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  dbc.Container([
34
  dbc.Row([
35
  dbc.Col([
@@ -85,13 +131,14 @@ app.layout = html.Div([
85
  dcc.Interval(id='interval-component', interval=1*1000, n_intervals=0)
86
  ])
87
 
88
- def add_log(message):
89
- global log_messages
90
- logger.info(message)
91
- log_messages.insert(0, f"{time.strftime('%H:%M:%S')} - {message}")
 
92
 
93
- def process_document(text):
94
- add_log("Starting document processing...")
95
  prompt = f"""
96
  Create a PowerPoint presentation based on the following document.
97
  Generate slides with titles and content. Format the output as markdown,
@@ -103,13 +150,13 @@ def process_document(text):
103
  Document:
104
  {text}
105
  """
106
- add_log("Sending request to Gemini model...")
107
  response = model.generate_content(prompt)
108
- add_log("Received response from Gemini model.")
109
  return response.text
110
 
111
- def generate_image_prompt(slide_content):
112
- add_log(f"Generating image prompt for slide: {slide_content[:30]}...")
113
  prompt = f"""
114
  Based on the following slide content, create a detailed prompt for generating
115
  an image that represents the main idea of the slide. The image should be
@@ -119,11 +166,11 @@ def generate_image_prompt(slide_content):
119
  Image prompt:
120
  """
121
  response = model.generate_content(prompt)
122
- add_log("Image prompt generated.")
123
  return response.text
124
 
125
- def generate_image(prompt):
126
- add_log(f"Generating image for prompt: {prompt[:30]}...")
127
  url = "https://api.stability.ai/v2beta/stable-image/generate/sd3"
128
  headers = {
129
  "Authorization": f"Bearer {STABILITY_API_KEY}",
@@ -137,25 +184,25 @@ def generate_image(prompt):
137
  try:
138
  response = requests.post(url, headers=headers, data=data, files=files)
139
  response.raise_for_status()
140
- add_log("Image generated successfully.")
141
  return response.content
142
  except requests.exceptions.RequestException as e:
143
- add_log(f"Error generating image: {str(e)}")
144
  return None
145
 
146
- def markdown_to_pptx(md_text):
147
- add_log("Starting PowerPoint generation...")
148
  prs = Presentation()
149
  slides = re.split(r'# Slide \d+:', md_text)
150
  slides = [slide.strip() for slide in slides if slide.strip()]
151
  with ThreadPoolExecutor() as executor:
152
  futures = []
153
  for i, slide_content in enumerate(slides):
154
- add_log(f"Processing slide {i+1}/{len(slides)}...")
155
- image_prompt = generate_image_prompt(slide_content)
156
- futures.append(executor.submit(generate_image, image_prompt))
157
  for i, (slide_content, future) in enumerate(zip(slides, futures)):
158
- add_log(f"Creating slide {i+1}/{len(slides)}...")
159
  lines = slide_content.split('\n')
160
  title = lines[0].strip()
161
  current_slide = prs.slides.add_slide(prs.slide_layouts[6])
@@ -178,7 +225,7 @@ def markdown_to_pptx(md_text):
178
  p.font.size = Pt(18)
179
  p.level = 1
180
  p.bullet = True
181
- add_log(f"Adding image to slide {i+1}...")
182
  image_data = future.result()
183
  if image_data:
184
  image_stream = io.BytesIO(image_data)
@@ -188,20 +235,35 @@ def markdown_to_pptx(md_text):
188
  img_width = Inches(4)
189
  img_height = img_width / aspect_ratio
190
  current_slide.shapes.add_picture(image_stream, Inches(5.5), Inches(1.5), width=img_width, height=img_height)
191
- add_log(f"Image added successfully to slide {i+1}.")
192
  except Exception as e:
193
- add_log(f"Error adding image to slide {i+1}: {str(e)}")
194
  placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6))
195
  placeholder.text_frame.text = "Image addition failed"
196
  else:
197
- add_log(f"Failed to generate image for slide {i+1}: {title}")
198
  placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6))
199
  placeholder.text_frame.text = "Image generation failed"
200
- add_log("PowerPoint generation completed.")
201
  output = io.BytesIO()
202
  prs.save(output)
203
  return output.getvalue()
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  @app.callback(
206
  Output('upload-file-name', 'children'),
207
  Input('upload-file', 'filename')
@@ -216,11 +278,14 @@ def update_filename(filename):
216
  Output('download-ppt-button', 'disabled'),
217
  Input('generate-button', 'n_clicks'),
218
  State('upload-file', 'contents'),
219
- State('document-text', 'value')
 
220
  )
221
- def generate_slides(n_clicks, file_contents, document_text):
222
  if n_clicks is None:
223
  raise PreventUpdate
 
 
224
  decoded_file_text = None
225
  if file_contents:
226
  content_type, content_string = file_contents.split(',')
@@ -232,7 +297,6 @@ def generate_slides(n_clicks, file_contents, document_text):
232
  decoded_file_text = decoded.decode('latin-1')
233
  except Exception:
234
  return "Could not decode uploaded file as text. Please upload a UTF-8 or Latin-1 encoded text file.", True
235
- # Combine both file and text if both are present
236
  combined_text = ""
237
  if decoded_file_text and document_text:
238
  combined_text = decoded_file_text.strip() + "\n\n" + document_text.strip()
@@ -244,12 +308,12 @@ def generate_slides(n_clicks, file_contents, document_text):
244
  return "Please upload a file or enter text.", True
245
 
246
  try:
247
- add_log("Starting slide generation...")
248
- slides_markdown = process_document(combined_text)
249
- add_log("Slide generation completed.")
250
  return slides_markdown, False
251
  except Exception as e:
252
- add_log(f"An error occurred during slide generation: {str(e)}")
253
  return f"An error occurred: {str(e)}", True
254
 
255
  @app.callback(
@@ -257,41 +321,54 @@ def generate_slides(n_clicks, file_contents, document_text):
257
  Output('download-ppt-button', 'disabled', allow_duplicate=True),
258
  Input('generate-ppt-button', 'n_clicks'),
259
  State('slides-content', 'value'),
 
260
  prevent_initial_call=True
261
  )
262
- def generate_powerpoint(n_clicks, slides_content):
263
- global generated_pptx
264
- if n_clicks is None or not slides_content:
265
  raise PreventUpdate
266
- try:
267
- add_log("Starting PowerPoint generation...")
268
- generated_pptx = markdown_to_pptx(slides_content)
269
- add_log("PowerPoint generation completed.")
270
- return '\n'.join(log_messages[:100]), False
271
- except Exception as e:
272
- error_message = f"An error occurred during PowerPoint generation: {str(e)}"
273
- add_log(error_message)
274
- return '\n'.join(log_messages[:100]), True
 
 
 
275
 
276
  @app.callback(
277
  Output("download-pptx", "data"),
278
  Input('download-ppt-button', 'n_clicks'),
 
279
  prevent_initial_call=True
280
  )
281
- def download_powerpoint(n_clicks):
282
- if n_clicks is None or generated_pptx is None:
283
  raise PreventUpdate
284
- return dcc.send_bytes(generated_pptx, "presentation.pptx")
 
 
 
 
285
 
286
  @app.callback(
287
  Output('log-output', 'children', allow_duplicate=True),
288
  Input('interval-component', 'n_intervals'),
 
289
  prevent_initial_call=True
290
  )
291
- def update_logs(n_intervals):
292
- return '\n'.join(log_messages[:100])
 
 
 
 
293
 
294
  if __name__ == '__main__':
295
  print("Starting the Dash application...")
296
- app.run(debug=True, host='0.0.0.0', port=7860)
297
  print("Dash application has finished running.")
 
14
  from PIL import Image
15
  import google.generativeai as genai
16
  import time
17
+ import uuid
18
+ import threading
19
 
20
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
21
  logger = logging.getLogger(__name__)
 
28
  app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP, 'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap'])
29
  app.config.suppress_callback_exceptions = True
30
 
31
+ # Session store: session_id -> {'log_messages': [], 'generated_pptx': None, 'lock': threading.Lock(), ...}
32
+ SESSION_DATA = {}
33
+ SESSION_DATA_LOCK = threading.Lock()
34
 
35
+ def get_session_id_from_cookie(cookies):
36
+ if not cookies:
37
+ return None
38
+ items = cookies.split(';')
39
+ for item in items:
40
+ k, sep, v = item.strip().partition('=')
41
+ if k == 'dash-session-id' and sep:
42
+ return v
43
+ return None
44
+
45
+ def get_or_create_session_data(session_id):
46
+ with SESSION_DATA_LOCK:
47
+ if session_id not in SESSION_DATA:
48
+ SESSION_DATA[session_id] = {
49
+ 'log_messages': [],
50
+ 'generated_pptx': None,
51
+ 'lock': threading.Lock()
52
+ }
53
+ return SESSION_DATA[session_id]
54
+
55
+ # Layout with dcc.Store and session id setting
56
  app.layout = html.Div([
57
+ dcc.Store(id='session-id', storage_type='local'),
58
+ # set session id cookie if not present
59
+ html.Script('''
60
+ (function() {
61
+ function uuidv4() {
62
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
63
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
64
+ return v.toString(16);
65
+ });
66
+ }
67
+ if (!document.cookie.split('; ').find(row => row.startsWith('dash-session-id='))) {
68
+ var sessionid = uuidv4();
69
+ document.cookie = 'dash-session-id=' + sessionid + '; path=/; SameSite=Lax';
70
+ window.localStorage.setItem('dash-session-id', sessionid);
71
+ } else {
72
+ var sessionid = document.cookie.split('; ').find(row => row.startsWith('dash-session-id=')).split('=')[1];
73
+ window.localStorage.setItem('dash-session-id', sessionid);
74
+ }
75
+ window.dash_clientside = window.dash_clientside || {};
76
+ window.dash_clientside.sessionid = sessionid;
77
+ })();
78
+ '''),
79
  dbc.Container([
80
  dbc.Row([
81
  dbc.Col([
 
131
  dcc.Interval(id='interval-component', interval=1*1000, n_intervals=0)
132
  ])
133
 
134
+ def add_log(message, session_id):
135
+ session = get_or_create_session_data(session_id)
136
+ with session['lock']:
137
+ logger.info(message)
138
+ session['log_messages'].insert(0, f"{time.strftime('%H:%M:%S')} - {message}")
139
 
140
+ def process_document(text, session_id):
141
+ add_log("Starting document processing...", session_id)
142
  prompt = f"""
143
  Create a PowerPoint presentation based on the following document.
144
  Generate slides with titles and content. Format the output as markdown,
 
150
  Document:
151
  {text}
152
  """
153
+ add_log("Sending request to Gemini model...", session_id)
154
  response = model.generate_content(prompt)
155
+ add_log("Received response from Gemini model.", session_id)
156
  return response.text
157
 
158
+ def generate_image_prompt(slide_content, session_id):
159
+ add_log(f"Generating image prompt for slide: {slide_content[:30]}...", session_id)
160
  prompt = f"""
161
  Based on the following slide content, create a detailed prompt for generating
162
  an image that represents the main idea of the slide. The image should be
 
166
  Image prompt:
167
  """
168
  response = model.generate_content(prompt)
169
+ add_log("Image prompt generated.", session_id)
170
  return response.text
171
 
172
+ def generate_image(prompt, session_id):
173
+ add_log(f"Generating image for prompt: {prompt[:30]}...", session_id)
174
  url = "https://api.stability.ai/v2beta/stable-image/generate/sd3"
175
  headers = {
176
  "Authorization": f"Bearer {STABILITY_API_KEY}",
 
184
  try:
185
  response = requests.post(url, headers=headers, data=data, files=files)
186
  response.raise_for_status()
187
+ add_log("Image generated successfully.", session_id)
188
  return response.content
189
  except requests.exceptions.RequestException as e:
190
+ add_log(f"Error generating image: {str(e)}", session_id)
191
  return None
192
 
193
+ def markdown_to_pptx(md_text, session_id):
194
+ add_log("Starting PowerPoint generation...", session_id)
195
  prs = Presentation()
196
  slides = re.split(r'# Slide \d+:', md_text)
197
  slides = [slide.strip() for slide in slides if slide.strip()]
198
  with ThreadPoolExecutor() as executor:
199
  futures = []
200
  for i, slide_content in enumerate(slides):
201
+ add_log(f"Processing slide {i+1}/{len(slides)}...", session_id)
202
+ image_prompt = generate_image_prompt(slide_content, session_id)
203
+ futures.append(executor.submit(generate_image, image_prompt, session_id))
204
  for i, (slide_content, future) in enumerate(zip(slides, futures)):
205
+ add_log(f"Creating slide {i+1}/{len(slides)}...", session_id)
206
  lines = slide_content.split('\n')
207
  title = lines[0].strip()
208
  current_slide = prs.slides.add_slide(prs.slide_layouts[6])
 
225
  p.font.size = Pt(18)
226
  p.level = 1
227
  p.bullet = True
228
+ add_log(f"Adding image to slide {i+1}...", session_id)
229
  image_data = future.result()
230
  if image_data:
231
  image_stream = io.BytesIO(image_data)
 
235
  img_width = Inches(4)
236
  img_height = img_width / aspect_ratio
237
  current_slide.shapes.add_picture(image_stream, Inches(5.5), Inches(1.5), width=img_width, height=img_height)
238
+ add_log(f"Image added successfully to slide {i+1}.", session_id)
239
  except Exception as e:
240
+ add_log(f"Error adding image to slide {i+1}: {str(e)}", session_id)
241
  placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6))
242
  placeholder.text_frame.text = "Image addition failed"
243
  else:
244
+ add_log(f"Failed to generate image for slide {i+1}: {title}", session_id)
245
  placeholder = current_slide.shapes.add_textbox(Inches(5.5), Inches(1.5), Inches(4), Inches(6))
246
  placeholder.text_frame.text = "Image generation failed"
247
+ add_log("PowerPoint generation completed.", session_id)
248
  output = io.BytesIO()
249
  prs.save(output)
250
  return output.getvalue()
251
 
252
+ @app.callback(
253
+ Output('session-id', 'data'),
254
+ Input('interval-component', 'n_intervals')
255
+ )
256
+ def store_session_id(n_intervals):
257
+ # Get from browser localStorage via JS (see layout). This value is available clientside.
258
+ # Dash will update session-id dcc.Store once on page load.
259
+ import flask
260
+ cookies = flask.request.cookies
261
+ session_id = cookies.get('dash-session-id')
262
+ if not session_id:
263
+ # Should not happen, JS sets cookie, but fallback for safety
264
+ session_id = str(uuid.uuid4())
265
+ return session_id
266
+
267
  @app.callback(
268
  Output('upload-file-name', 'children'),
269
  Input('upload-file', 'filename')
 
278
  Output('download-ppt-button', 'disabled'),
279
  Input('generate-button', 'n_clicks'),
280
  State('upload-file', 'contents'),
281
+ State('document-text', 'value'),
282
+ State('session-id', 'data')
283
  )
284
+ def generate_slides(n_clicks, file_contents, document_text, session_id):
285
  if n_clicks is None:
286
  raise PreventUpdate
287
+ if not session_id:
288
+ raise PreventUpdate
289
  decoded_file_text = None
290
  if file_contents:
291
  content_type, content_string = file_contents.split(',')
 
297
  decoded_file_text = decoded.decode('latin-1')
298
  except Exception:
299
  return "Could not decode uploaded file as text. Please upload a UTF-8 or Latin-1 encoded text file.", True
 
300
  combined_text = ""
301
  if decoded_file_text and document_text:
302
  combined_text = decoded_file_text.strip() + "\n\n" + document_text.strip()
 
308
  return "Please upload a file or enter text.", True
309
 
310
  try:
311
+ add_log("Starting slide generation...", session_id)
312
+ slides_markdown = process_document(combined_text, session_id)
313
+ add_log("Slide generation completed.", session_id)
314
  return slides_markdown, False
315
  except Exception as e:
316
+ add_log(f"An error occurred during slide generation: {str(e)}", session_id)
317
  return f"An error occurred: {str(e)}", True
318
 
319
  @app.callback(
 
321
  Output('download-ppt-button', 'disabled', allow_duplicate=True),
322
  Input('generate-ppt-button', 'n_clicks'),
323
  State('slides-content', 'value'),
324
+ State('session-id', 'data'),
325
  prevent_initial_call=True
326
  )
327
+ def generate_powerpoint(n_clicks, slides_content, session_id):
328
+ if n_clicks is None or not slides_content or not session_id:
 
329
  raise PreventUpdate
330
+ session = get_or_create_session_data(session_id)
331
+ with session['lock']:
332
+ try:
333
+ add_log("Starting PowerPoint generation...", session_id)
334
+ pptx_bytes = markdown_to_pptx(slides_content, session_id)
335
+ session['generated_pptx'] = pptx_bytes
336
+ add_log("PowerPoint generation completed.", session_id)
337
+ return '\n'.join(session['log_messages'][:100]), False
338
+ except Exception as e:
339
+ error_message = f"An error occurred during PowerPoint generation: {str(e)}"
340
+ add_log(error_message, session_id)
341
+ return '\n'.join(session['log_messages'][:100]), True
342
 
343
  @app.callback(
344
  Output("download-pptx", "data"),
345
  Input('download-ppt-button', 'n_clicks'),
346
+ State('session-id', 'data'),
347
  prevent_initial_call=True
348
  )
349
+ def download_powerpoint(n_clicks, session_id):
350
+ if n_clicks is None or not session_id:
351
  raise PreventUpdate
352
+ session = get_or_create_session_data(session_id)
353
+ with session['lock']:
354
+ if session['generated_pptx'] is None:
355
+ raise PreventUpdate
356
+ return dcc.send_bytes(session['generated_pptx'], "presentation.pptx")
357
 
358
  @app.callback(
359
  Output('log-output', 'children', allow_duplicate=True),
360
  Input('interval-component', 'n_intervals'),
361
+ State('session-id', 'data'),
362
  prevent_initial_call=True
363
  )
364
+ def update_logs(n_intervals, session_id):
365
+ if not session_id:
366
+ return ""
367
+ session = get_or_create_session_data(session_id)
368
+ with session['lock']:
369
+ return '\n'.join(session['log_messages'][:100])
370
 
371
  if __name__ == '__main__':
372
  print("Starting the Dash application...")
373
+ app.run(debug=True, host='0.0.0.0', port=7860, threaded=True)
374
  print("Dash application has finished running.")