import streamlit as st import os import time import logging import tempfile from pptx import Presentation from pptx.util import Inches from pptx.dml.color import RGBColor from pptx.enum.text import PP_ALIGN import openai import requests from requests.exceptions import RequestException import base64 # Konfiguracja logowania logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s') logger = logging.getLogger(__name__) # Funkcja do usuwania formatowania Markdown def remove_markdown_formatting(text): return text.replace('**', '').replace('#', '').strip() # Funkcja do podziału tekstu na moduły (slajdy) def split_into_modules(content): modules = content.split('####') modules = [module.strip() for module in modules if module.strip()] # Logowanie modułów do debugowania for i, module in enumerate(modules): logger.info(f"Moduł {i + 1}: {module[:100]}...") # Logowanie pierwszych 100 znaków modułu return modules # Funkcja do tworzenia prezentacji z modułów def create_ppt_from_modules(modules, output_file, aspect_ratio="16:9"): prs = Presentation() # Ustawienie formatu slajdów if aspect_ratio == "4:3": prs.slide_width = Inches(10) prs.slide_height = Inches(7.5) else: # 16:9 prs.slide_width = Inches(13.33) prs.slide_height = Inches(7.5) for module in modules: try: slide = prs.slides.add_slide(prs.slide_layouts[1]) title, *body = module.split('\n', 1) # Formatowanie tytułu slajdu title_shape = slide.shapes.title title_shape.text = remove_markdown_formatting(title) title_paragraph = title_shape.text_frame.paragraphs[0] title_paragraph.font.size = Inches(0.5) # Rozmiar tytułu title_paragraph.font.name = 'Helvetica' title_paragraph.alignment = PP_ALIGN.LEFT # Formatowanie treści slajdu if body: content = remove_markdown_formatting(body[0].strip()) text_frame = slide.placeholders[1].text_frame text_frame.text = content for paragraph in text_frame.paragraphs: if paragraph.text.strip(): # Sprawdź, czy akapit ma tekst paragraph.font.size = Inches(0.25) # Rozmiar tekstu paragraph.font.name = 'Helvetica' paragraph.alignment = PP_ALIGN.LEFT else: logger.warning(f"Pusty akapit w module: {title}") else: logger.warning(f"Brak treści w module: {title}") except Exception as e: logger.error(f"Błąd podczas przetwarzania modułu: {module}. Błąd: {e}") st.error(f"Błąd podczas przetwarzania modułu: {module}. Błąd: {e}") prs.save(output_file) logger.info(f"Prezentacja została zapisana jako {output_file}") # Funkcja do generowania treści prezentacji za pomocą OpenAI def generate_presentation_content_openai(prompt, api_key, progress_bar, progress_text): try: headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } data = { "model": "gpt-4o", "messages": [ {"role": "system", "content": "Jesteś ekspertem w tworzeniu prezentacji." "Twórz treść prezentacji w formacie, gdzie każdy slajd zaczyna się od '#### Tytuł slajdu' a następnie zawiera treść slajdu. Używaj języka polskiego." "Nie dodawaj jednak frazy 'Tytuł slajdu:' tylko od razu wstaw adekwatny tytuł. Po tytule umieść treść slajdu."}, {"role": "user", "content": prompt} ], "stream": True, "max_tokens": 8000 } # Wykonanie żądania ze streamowaniem response = requests.post( "https://api.openai.com/v1/chat/completions", json=data, headers=headers, stream=True, # Możemy dodać proxy, jeśli jest to wymagane proxies={ "http": os.getenv("HTTP_PROXY", ""), "https": os.getenv("HTTPS_PROXY", "") } ) if response.status_code != 200: logger.error(f"Błąd OpenAI API: {response.status_code} - {response.text}") st.error(f"Błąd OpenAI API: {response.status_code} - {response.text}") return None # Przetwarzanie streamowanej odpowiedzi full_content = "" for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith("data:") and not line.startswith("data: [DONE]"): try: json_str = line[5:].strip() if json_str: import json data = json.loads(json_str) if "choices" in data and len(data["choices"]) > 0: choice = data["choices"][0] if "delta" in choice and "content" in choice["delta"] and choice["delta"]["content"]: content_chunk = choice["delta"]["content"] full_content += content_chunk progress_text.markdown(full_content) # Symulacja postępu progress_bar.progress(min(len(full_content) / 3000, 1.0)) except Exception as e: logger.error(f"Błąd podczas parsowania odpowiedzi OpenAI: {e}") progress_bar.progress(1.0) return full_content except RequestException as e: logger.error(f"Błąd połączenia z OpenAI API: {e}") st.error(f"Błąd połączenia z OpenAI API: {e}") return None except Exception as e: logger.error(f"Błąd podczas generowania treści przez OpenAI: {e}") st.error(f"Błąd podczas generowania treści przez OpenAI: {e}") return None # Funkcja do generowania treści prezentacji za pomocą DeepSeek def generate_presentation_content_deepseek(prompt, api_key, progress_bar, progress_text): try: headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } data = { "model": "deepseek-chat", "messages": [ {"role": "system", "content": "Jesteś ekspertem w tworzeniu prezentacji." "Twórz treść prezentacji w formacie, gdzie każdy slajd zaczyna się od '#### Tytuł slajdu' a następnie zawiera treść slajdu. Używaj języka polskiego." "Nie dodawaj jednak frazy 'Tytuł slajdu:' tylko od razu wstaw adekwatny tytuł. Po tytule umieść treść slajdu."}, {"role": "user", "content": prompt} ], "stream": True, "max_tokens": 8000 } # Wykonanie żądania ze streamowaniem response = requests.post( "https://api.deepseek.com/chat/completions", json=data, headers=headers, stream=True ) if response.status_code != 200: logger.error(f"Błąd DeepSeek API: {response.status_code} - {response.text}") st.error(f"Błąd DeepSeek API: {response.status_code} - {response.text}") return None # Przetwarzanie streamowanej odpowiedzi full_content = "" for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith("data:") and not line.startswith("data: [DONE]"): try: json_str = line[5:].strip() if json_str: import json data = json.loads(json_str) if "choices" in data and len(data["choices"]) > 0: choice = data["choices"][0] if "delta" in choice and "content" in choice["delta"] and choice["delta"]["content"]: content_chunk = choice["delta"]["content"] full_content += content_chunk progress_text.markdown(full_content) # Symulacja postępu progress_bar.progress(min(len(full_content) / 3000, 1.0)) except Exception as e: logger.error(f"Błąd podczas parsowania odpowiedzi DeepSeek: {e}") progress_bar.progress(1.0) return full_content except RequestException as e: logger.error(f"Błąd połączenia z DeepSeek API: {e}") st.error(f"Błąd połączenia z DeepSeek API: {e}") return None except Exception as e: logger.error(f"Błąd podczas generowania treści przez DeepSeek: {e}") st.error(f"Błąd podczas generowania treści przez DeepSeek: {e}") return None # Funkcja do pobierania pliku def get_binary_file_downloader_html(file_path, file_label='File'): with open(file_path, 'rb') as f: data = f.read() b64 = base64.b64encode(data).decode() href = f'📥 Pobierz prezentację PowerPoint' return href # Główna funkcja aplikacji Streamlit def main(): st.set_page_config(page_title="Generator Prezentacji PPT", layout="wide") st.title("🎯 Generator Prezentacji PPT") st.write("Ta aplikacja generuje prezentacje PowerPoint na dowolny temat za pomocą sztucznej inteligencji.") # Wybór modelu model = st.selectbox( "Wybierz model LLM:", ["GPT-4o", "DeepSeek"] ) # Wybór formatu prezentacji aspect_ratio = st.selectbox( "Wybierz format prezentacji:", ["16:9", "4:3"] ) # Wprowadzenie klucza API api_key = st.text_input("Wprowadź klucz API", type="password", help="Twój klucz API do wybranego modelu") # Wprowadzenie tematu prezentacji topic = st.text_area("Wprowadź temat prezentacji lub szczegółowe instrukcje:", height=150) # Tworzenie tymczasowego katalogu na pliki temp_dir = tempfile.mkdtemp() output_file = os.path.join(temp_dir, "prezentacja.pptx") if st.button("Generuj Prezentację", type="primary"): if not api_key: st.error("Proszę wprowadzić klucz API.") return if not topic: st.error("Proszę wprowadzić temat prezentacji.") return # Przygotowanie promptu prompt = f""" Stwórz prezentację na temat: "{topic}". Każdy slajd powinien zaczynać się od #### Tytuł slajdu Po tytule powinien być umieszczony tekst slajdu. Stwórz slajdy, które będą zawierać kompleksowe informacje o podanym temacie. Rozpocznij od slajdu tytułowego, a zakończ slajdem podsumowującym. """ st.write("### Generowanie treści prezentacji...") # Pasek postępu i miejsce na wyświetlanie generowanego tekstu progress_bar = st.progress(0) progress_text = st.empty() # Generowanie treści prezentacji w zależności od wybranego modelu if model == "GPT-4o": content = generate_presentation_content_openai(prompt, api_key, progress_bar, progress_text) else: # DeepSeek content = generate_presentation_content_deepseek(prompt, api_key, progress_bar, progress_text) content = content.replace("\r", "").replace("\n\n---\n\n", "\n\n").replace("\n---\n", "\n").replace("---", "") # usuwanie zbędnych separatorów if content: st.success("✅ Treść prezentacji została wygenerowana!") # Podział na moduły (slajdy) st.write("### Przetwarzanie slajdów...") modules = split_into_modules(content) st.info(f"Liczba wygenerowanych slajdów: {len(modules)}") # Tworzenie prezentacji PowerPoint with st.spinner("Tworzenie pliku PowerPoint..."): create_ppt_from_modules(modules, output_file, aspect_ratio) st.success(f"✅ Prezentacja PowerPoint została utworzona!") # Przycisk do pobrania prezentacji st.markdown(get_binary_file_downloader_html(output_file), unsafe_allow_html=True) # Wyświetl treść wygenerowanej prezentacji with st.expander("Zobacz wygenerowaną treść prezentacji"): st.write(content) else: st.error("❌ Nie udało się wygenerować treści prezentacji. Sprawdź logi i klucz API.") # Dodaj informacje o aplikacji st.markdown("---") st.markdown("### 📝 Informacje o aplikacji") st.markdown(""" - Aplikacja używa AI do generowania treści prezentacji. - Format wyjściowy: PowerPoint (.pptx). - Wspierane modele: GPT-4o (OpenAI) i DeepSeek. """) # Dodaj informacje o użyciu API st.markdown("### 🔑 Informacje o użyciu API") st.markdown(""" - Dla GPT-4o: Użyj klucza API OpenAI ze swojego konta: https://platform.openai.com/api-keys - Dla DeepSeek: Użyj klucza API DeepSeek ze swojego konta: https://platform.deepseek.com/api_keys - Twój klucz API jest używany tylko do generowania treści i nie jest nigdzie przechowywany. """) if __name__ == "__main__": try: main() except Exception as e: st.error(f"Wystąpił nieoczekiwany błąd: {str(e)}") logger.exception("Nieoczekiwany błąd aplikacji:")