Spaces:
Running
Running
import streamlit as st | |
import concurrent.futures | |
import asyncio | |
import aiohttp | |
from datetime import datetime | |
from fpdf import FPDF | |
import os | |
from dotenv import load_dotenv | |
from transformers import pipeline | |
import torch | |
import nltk | |
from nltk.tokenize import sent_tokenize, word_tokenize | |
from nltk.tag import pos_tag | |
from PIL import Image | |
import textwrap | |
import re | |
from typing import List, Dict, Optional | |
import numpy as np | |
from collections import defaultdict | |
from io import BytesIO | |
import matplotlib.pyplot as plt | |
import seaborn as sns | |
from faker import Faker | |
import wikipedia | |
from urllib.parse import quote | |
# Initialize NLTK and spaCy | |
nltk.download('punkt', quiet=True) | |
nltk.download('averaged_perceptron_tagger', quiet=True) | |
nltk.download('maxent_ne_chunker', quiet=True) | |
nltk.download('words', quiet=True) | |
fake = Faker() | |
# Load environment variables | |
load_dotenv() | |
PIXABAY_API_KEY = os.getenv('PIXABAY_API_KEY') | |
class EnhancedBookGenerator: | |
def _enhance_content(self, content: str, facts: Optional[List[str]] = None) -> str: | |
"""Enhance content with facts and structure""" | |
if facts is None: | |
facts = [] | |
# Clean text | |
content = re.sub(r'\s+', ' ', content).strip() | |
# Process with NLTK | |
sentences = sent_tokenize(content) | |
# Add structure and facts | |
structured_content = [] | |
current_section = [] | |
fact_index = 0 | |
for i, sentence in enumerate(sentences): | |
current_section.append(sentence) | |
# Add a fact every few sentences if available | |
if facts and i % 5 == 0 and fact_index < len(facts): | |
current_section.append(f"\nInteresting fact: {facts[fact_index]}\n") | |
fact_index += 1 | |
if (i + 1) % 5 == 0: | |
section_text = ' '.join(current_section) | |
if self.summarizer: | |
heading = self.summarizer(section_text, max_length=10, min_length=5)[0]['summary_text'] | |
structured_content.append(f"\n## {heading.title()}\n") | |
structured_content.append(section_text) | |
current_section = [] | |
if current_section: | |
structured_content.append(' '.join(current_section)) | |
return '\n'.join(structured_content) | |
def __init__(self): | |
self.initialize_models() | |
self.initialize_quality_metrics() | |
def initialize_models(self): | |
"""Initialize NLP models with optimization""" | |
try: | |
# Use smaller, faster models for better performance | |
self.text_generator = pipeline( | |
"text-generation", | |
model="distilgpt2", | |
device=0 if torch.cuda.is_available() else -1 | |
) | |
self.summarizer = pipeline( | |
"summarization", | |
model="facebook/bart-large-cnn", | |
device=0 if torch.cuda.is_available() else -1 | |
) | |
except Exception as e: | |
st.error(f"Error initializing models: {str(e)}") | |
self.text_generator = None | |
self.summarizer = None | |
def initialize_quality_metrics(self): | |
"""Initialize text quality metrics""" | |
self.metrics = { | |
'readability': defaultdict(float), | |
'coherence': defaultdict(float), | |
'style_match': defaultdict(float) | |
} | |
def process_text(self, text: str) -> str: | |
"""Process text using NLTK instead of spaCy""" | |
sentences = sent_tokenize(text) | |
words = word_tokenize(text) | |
tagged = pos_tag(words) | |
return ' '.join(sentences) | |
async def fetch_relevant_facts(self, topic: str) -> List[str]: | |
"""Fetch relevant facts from Wikipedia""" | |
try: | |
search_results = wikipedia.search(topic, results=3) | |
facts = [] | |
for title in search_results: | |
try: | |
page = wikipedia.page(title) | |
summary = wikipedia.summary(title, sentences=2) | |
facts.append(summary) | |
except: | |
continue | |
return facts | |
except: | |
return [] | |
async def generate_chapter_content(self, chapter_info: Dict) -> str: | |
"""Generate chapter content with parallel processing""" | |
try: | |
prompt = self._create_chapter_prompt(chapter_info) | |
# Parallel tasks | |
tasks = [ | |
self._generate_raw_content(prompt, chapter_info['word_count']), | |
self.fetch_relevant_facts(chapter_info['title']) | |
] | |
# Run tasks concurrently | |
results = await asyncio.gather(*tasks) | |
content, facts = results | |
# Enhance content with facts | |
enhanced_content = self._enhance_content(content, facts) | |
return enhanced_content | |
except Exception as e: | |
st.error(f"Error generating chapter: {str(e)}") | |
return f"Error generating chapter {chapter_info['title']}" | |
def _create_chapter_prompt(self, chapter_info: Dict) -> str: | |
"""Create detailed writing prompt with style and context""" | |
base_prompt = super()._create_chapter_prompt(chapter_info) | |
# Add user preferences if available | |
if 'user_prompt' in chapter_info: | |
base_prompt += f"\nConsider the following preferences: {chapter_info['user_prompt']}" | |
return base_prompt | |
def initialize_quality_metrics(self): | |
"""Initialize text quality metrics""" | |
self.metrics = { | |
'readability': defaultdict(float), | |
'coherence': defaultdict(float), | |
'style_match': defaultdict(float) | |
} | |
def generate_chapter_content(self, chapter_info: Dict) -> str: | |
"""Generate chapter content incorporating user prompt""" | |
try: | |
# Include user prompt in chapter generation | |
base_prompt = self._create_chapter_prompt(chapter_info) | |
user_prompt = st.session_state.book_settings.get('user_prompt', '') | |
if user_prompt: | |
enhanced_prompt = f""" | |
User's book vision: {user_prompt} | |
Based on this vision, write a chapter that fits the following: | |
{base_prompt} | |
""" | |
else: | |
enhanced_prompt = base_prompt | |
content = self._generate_raw_content(enhanced_prompt, chapter_info['word_count']) | |
# Pass empty list for facts if none available | |
enhanced_content = self._enhance_content(content, facts=[]) | |
return enhanced_content | |
except Exception as e: | |
st.error(f"Error generating chapter: {str(e)}") | |
return f"Error generating chapter {chapter_info['title']}" | |
def _create_chapter_prompt(self, chapter_info: Dict) -> str: | |
"""Create detailed writing prompt based on style and context""" | |
style_prompts = { | |
"48 Laws of Power Style": "Write an authoritative chapter about power dynamics and strategy", | |
"The Prince Style": "Write a pragmatic chapter about leadership and governance", | |
"Modern Business Book": "Write an insightful chapter about business strategy and success", | |
"Self-Help Book": "Write an empowering chapter about personal development", | |
"Novel": "Write an engaging narrative chapter with vivid descriptions" | |
} | |
return f"{style_prompts.get(chapter_info['style'], 'Write a chapter')} titled '{chapter_info['title']}'" | |
def _generate_raw_content(self, prompt: str, word_count: int) -> str: | |
"""Generate content in chunks""" | |
if not self.text_generator: | |
return f"Sample content for {prompt}" | |
content_parts = [] | |
remaining_words = min(word_count, 2000) # Limit for stability | |
while remaining_words > 0: | |
chunk_size = min(remaining_words, 500) | |
generated = self.text_generator( | |
prompt, | |
max_length=chunk_size, | |
num_return_sequences=1, | |
temperature=0.8 | |
)[0]['generated_text'] | |
content_parts.append(generated) | |
remaining_words -= len(generated.split()) | |
return ' '.join(content_parts) | |
def _enhance_content(self, content: str, facts: Optional[List[str]] = None) -> str: | |
"""Enhance content with facts and structure""" | |
if facts is None: | |
facts = [] | |
# Clean text | |
content = re.sub(r'\s+', ' ', content).strip() | |
# Process with NLTK | |
sentences = sent_tokenize(content) | |
# Add structure and facts | |
structured_content = [] | |
current_section = [] | |
fact_index = 0 | |
for i, sentence in enumerate(sentences): | |
current_section.append(sentence) | |
# Add a fact every few sentences if available | |
if facts and i % 5 == 0 and fact_index < len(facts): | |
current_section.append(f"\nInteresting fact: {facts[fact_index]}\n") | |
fact_index += 1 | |
if (i + 1) % 5 == 0: | |
section_text = ' '.join(current_section) | |
if self.summarizer: | |
heading = self.summarizer(section_text, max_length=10, min_length=5)[0]['summary_text'] | |
structured_content.append(f"\n## {heading.title()}\n") | |
structured_content.append(section_text) | |
current_section = [] | |
if current_section: | |
structured_content.append(' '.join(current_section)) | |
return '\n'.join(structured_content) | |
class EnhancedPDFFormatter(FPDF): | |
def __init__(self, style: Dict): | |
super().__init__() | |
self.style = style | |
self.set_auto_page_break(auto=True, margin=15) | |
self.add_font('DejaVu', '', 'DejaVuSansCondensed.ttf', uni=True) | |
def initialize_styles(self): | |
"""Initialize PDF styling templates""" | |
self.styles = { | |
"48 Laws of Power Style": { | |
"title_font": ("Times", 24, "B"), | |
"chapter_font": ("Times", 18, "B"), | |
"body_font": ("Times", 12, ""), | |
"margins": 25, | |
"line_height": 1.5 | |
}, | |
"The Prince Style": { | |
"title_font": ("Helvetica", 24, "B"), | |
"chapter_font": ("Helvetica", 18, "B"), | |
"body_font": ("Helvetica", 12, ""), | |
"margins": 30, | |
"line_height": 1.6 | |
}, | |
# Add more styles as needed | |
} | |
def chapter_title(self, title: str): | |
"""Add formatted chapter title""" | |
self.set_font(*self.style["chapter_font"]) | |
self.set_fill_color(240, 240, 240) | |
self.cell(0, 20, title, ln=True, fill=True) | |
self.ln(10) | |
def chapter_body(self, content: str): | |
"""Add formatted chapter content""" | |
self.set_font(*self.style["body_font"]) | |
self.multi_cell(0, self.style["line_height"] * 10, content) | |
self.ln() | |
def add_footer(self): | |
"""Add page numbers and metadata""" | |
self.set_y(-15) | |
self.set_font("Arial", "I", 8) | |
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C') | |
def create_pdf(self, book_content: Dict) -> BytesIO: | |
"""Create formatted PDF""" | |
try: | |
pdf = FPDF() | |
style = self.styles.get(book_content.get('style', "48 Laws of Power Style")) | |
# Set basic formatting | |
pdf.set_margins(style["margins"], style["margins"], style["margins"]) | |
# Add title page | |
pdf.add_page() | |
pdf.set_font(*style["title_font"]) | |
pdf.cell(0, 60, book_content['title'], ln=True, align='C') | |
# Add chapters | |
for chapter in book_content['chapters']: | |
pdf.add_page() | |
pdf.set_font(*style["chapter_font"]) | |
pdf.cell(0, 20, chapter['title'], ln=True) | |
pdf.set_font(*style["body_font"]) | |
pdf.multi_cell(0, style["line_height"] * 10, chapter['content']) | |
# Return PDF as BytesIO | |
pdf_output = BytesIO() | |
pdf.output(pdf_output) | |
pdf_output.seek(0) | |
return pdf_output | |
except Exception as e: | |
st.error(f"Error creating PDF: {str(e)}") | |
return None | |
def initialize_session_state(): | |
"""Initialize enhanced session state with default values""" | |
if 'book_settings' not in st.session_state: | |
st.session_state.book_settings = { | |
'title': '', | |
'template': '', | |
'genre': '', | |
'num_chapters': 0, | |
'words_per_chapter': 0, | |
'chapter_titles': [], | |
'total_words': 0 | |
} | |
if 'generated_book' not in st.session_state: | |
st.session_state.generated_book = None | |
if 'current_step' not in st.session_state: | |
st.session_state.current_step = 'Book Settings' | |
if 'user_prompt' not in st.session_state: | |
st.session_state.user_prompt = "" | |
if 'author_name' not in st.session_state: | |
st.session_state.author_name = "" | |
if 'book_generator' not in st.session_state: | |
st.session_state.book_generator = EnhancedBookGenerator() | |
def main(): | |
st.set_page_config(page_title="AI Book Generator", layout="wide") | |
# Initialize session state at the start | |
initialize_session_state() | |
# Book templates | |
book_templates = { | |
"48 Laws of Power Style": { | |
"min_words": 80000, | |
"max_words": 100000, | |
"default_chapters": 48, | |
"example_titles": ["Law 1: Never Outshine the Master", "Law 2: Never Put Too Much Trust in Friends"] | |
}, | |
"The Prince Style": { | |
"min_words": 40000, | |
"max_words": 50000, | |
"default_chapters": 26, | |
"example_titles": ["On New Principalities", "On Military Affairs"] | |
}, | |
"Modern Business Book": { | |
"min_words": 50000, | |
"max_words": 70000, | |
"default_chapters": 12, | |
"example_titles": ["The Art of Strategy", "Building Success"] | |
}, | |
"Self-Help Book": { | |
"min_words": 40000, | |
"max_words": 60000, | |
"default_chapters": 10, | |
"example_titles": ["Understanding Your Potential", "Taking Action"] | |
}, | |
"Novel": { | |
"min_words": 70000, | |
"max_words": 100000, | |
"default_chapters": 20, | |
"example_titles": ["The Beginning", "Rising Action"] | |
} | |
} | |
# Sidebar navigation | |
with st.sidebar: | |
st.title("Navigation") | |
selected_step = st.radio( | |
"Steps", | |
["Book Settings", "Generate Content", "Preview & Download"] | |
) | |
st.session_state.current_step = selected_step | |
st.title("AI Book Generator") | |
# Main content area | |
if st.session_state.current_step == "Book Settings": | |
display_book_settings(book_templates) | |
elif st.session_state.current_step == "Generate Content": | |
display_generate_content() | |
elif st.session_state.current_step == "Preview & Download": | |
display_preview_download() | |
def display_book_settings(templates): | |
"""Display the book settings page with enhanced user prompts""" | |
st.header("Book Settings") | |
# User's creative prompt | |
st.subheader("Tell us about your book idea") | |
user_prompt = st.text_area( | |
"Describe your book idea, themes, feelings, and key elements you want to include", | |
value=st.session_state.get('user_prompt', ''), | |
height=150, | |
help="Be as detailed as possible. Include themes, mood, character ideas, plot elements, or any other aspects you want in your book.", | |
placeholder="Example: I want to write a book about a journey of self-discovery in a dystopian world. The main themes should include hope, resilience, and the power of human connection..." | |
) | |
st.session_state.user_prompt = user_prompt | |
col1, col2 = st.columns(2) | |
with col1: | |
title = st.text_input("Book Title", | |
value=st.session_state.book_settings.get('title', ''), | |
key="book_title_input") | |
template = st.selectbox("Writing Style", | |
options=list(templates.keys()), | |
key="template_select") | |
genre = st.selectbox("Genre", | |
options=["Business", "Self-Help", "Philosophy", | |
"Fiction", "Non-Fiction", "Historical", | |
"Scientific", "Political", "Educational"], | |
key="genre_select") | |
with col2: | |
num_chapters = st.number_input( | |
"Number of Chapters", | |
min_value=1, | |
max_value=50, | |
value=templates[template]["default_chapters"], | |
key="num_chapters_input" | |
) | |
words_per_chapter = st.number_input( | |
"Words per Chapter", | |
min_value=100, | |
max_value=5000, | |
value=1000, | |
key="words_per_chapter_input" | |
) | |
# Generate intelligent chapter titles based on user prompt | |
if user_prompt and st.button("Generate Chapter Titles"): | |
with st.spinner("Generating chapter titles based on your prompt..."): | |
chapter_titles = generate_intelligent_chapter_titles( | |
user_prompt, | |
num_chapters, | |
template, | |
genre | |
) | |
st.session_state.generated_chapter_titles = chapter_titles | |
else: | |
chapter_titles = st.session_state.get('generated_chapter_titles', | |
[f"Chapter {i+1}" for i in range(num_chapters)]) | |
# Display and allow editing of generated chapter titles | |
st.subheader("Chapter Titles") | |
edited_chapter_titles = [] | |
cols = st.columns(3) | |
for i in range(num_chapters): | |
with cols[i % 3]: | |
title = st.text_input( | |
f"Chapter {i+1}", | |
value=chapter_titles[i] if i < len(chapter_titles) else f"Chapter {i+1}", | |
key=f"chapter_title_{i}" | |
) | |
edited_chapter_titles.append(title) | |
if st.button("Save Settings", key="save_settings_button"): | |
st.session_state.book_settings = { | |
"title": title, | |
"template": template, | |
"genre": genre, | |
"num_chapters": num_chapters, | |
"words_per_chapter": words_per_chapter, | |
"chapter_titles": edited_chapter_titles, | |
"total_words": num_chapters * words_per_chapter, | |
"user_prompt": user_prompt | |
} | |
st.success("β Settings saved successfully! You can now proceed to Generate Content.") | |
def generate_intelligent_chapter_titles(prompt, num_chapters, template, genre): | |
"""Generate intelligent chapter titles based on user input""" | |
# This is a placeholder for the actual AI generation | |
# You would typically use an LLM API here | |
system_prompt = f""" | |
Based on the following user's book idea, generate {num_chapters} chapter titles. | |
The book is in the {genre} genre and follows the {template} style. | |
User's book idea: | |
{prompt} | |
Generate unique, engaging chapter titles that follow a logical progression | |
and reflect the themes and elements mentioned in the user's prompt. | |
""" | |
# For now, returning placeholder titles | |
# Replace this with actual AI generation | |
return [f"Generated Chapter {i+1}" for i in range(num_chapters)] | |
def display_generate_content(): | |
"""Display the content generation page""" | |
if not st.session_state.book_settings: | |
st.warning("β οΈ Please configure book settings first!") | |
return | |
st.header("Generate Content") | |
# Display current settings | |
st.subheader("Current Settings") | |
settings = st.session_state.book_settings | |
st.write(f"π Title: {settings['title']}") | |
st.write(f"π Style: {settings['template']}") | |
st.write(f"π Chapters: {settings['num_chapters']}") | |
st.write(f"π Total Words: {settings['total_words']}") | |
if st.button("Generate Book", key="generate_book_button"): | |
with st.spinner("π Generating your book... This may take a few minutes."): | |
progress_bar = st.progress(0) | |
chapters = [] | |
for i, title in enumerate(st.session_state.book_settings["chapter_titles"]): | |
chapter_info = { | |
"title": title, | |
"style": st.session_state.book_settings["template"], | |
"word_count": st.session_state.book_settings["words_per_chapter"] | |
} | |
content = st.session_state.book_generator.generate_chapter_content(chapter_info) | |
chapters.append({"title": title, "content": content}) | |
progress_bar.progress((i + 1) / len(st.session_state.book_settings["chapter_titles"])) | |
st.session_state.generated_book = { | |
"title": st.session_state.book_settings["title"], | |
"style": st.session_state.book_settings["template"], | |
"chapters": chapters | |
} | |
st.success("Book generated! Proceed to Preview & Download.") | |
def display_preview_download(): | |
"""Display the preview and download page""" | |
if not st.session_state.generated_book: | |
st.warning("β οΈ Please generate book content first!") | |
return | |
st.header("Preview & Download") | |
# Chapter preview | |
st.subheader("Chapter Preview") | |
book = st.session_state.generated_book | |
chapter_select = st.selectbox( | |
"Select chapter to preview", | |
options=[chapter["title"] for chapter in book["chapters"]], | |
key="chapter_preview_select" | |
) | |
selected_chapter = next( | |
chapter for chapter in book["chapters"] | |
if chapter["title"] == chapter_select | |
) | |
with st.expander("Chapter Content", expanded=True): | |
st.markdown(selected_chapter["content"]) | |
# Download section | |
st.subheader("Download Options") | |
if st.button("Generate PDF", key="generate_pdf_button"): | |
with st.spinner("π Creating PDF..."): | |
# Here you would call your PDF generation code | |
# For now, we'll just show a success message | |
st.success("β PDF generated! Click below to download.") | |
st.download_button( | |
label="β¬οΈ Download Book", | |
data=b"Sample PDF content", # Replace with actual PDF data | |
file_name=f"{book['title']}.pdf", | |
mime="application/pdf" | |
) | |
if __name__ == "__main__": | |
main() |