Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,414 +1,351 @@
|
|
| 1 |
# File: app.py
|
| 2 |
-
# Location: /app.py (root directory for Hugging Face Spaces)
|
| 3 |
-
# Description: Main Streamlit application for AI Book Writing Assistant
|
| 4 |
|
| 5 |
import streamlit as st
|
| 6 |
-
import os
|
| 7 |
import json
|
| 8 |
-
import
|
| 9 |
-
|
| 10 |
-
from
|
| 11 |
-
from persistence_manager import ProjectPersistenceManager
|
| 12 |
|
| 13 |
-
class
|
| 14 |
def __init__(self):
|
| 15 |
-
"""
|
| 16 |
-
Initialize the Streamlit application for book writing
|
| 17 |
-
|
| 18 |
-
Manages session state and persistence
|
| 19 |
-
"""
|
| 20 |
-
# Set page configuration
|
| 21 |
st.set_page_config(
|
| 22 |
-
page_title="
|
| 23 |
-
page_icon="
|
| 24 |
layout="wide"
|
| 25 |
)
|
| 26 |
|
| 27 |
-
# Initialize
|
| 28 |
-
self.
|
| 29 |
|
| 30 |
# Initialize session state
|
| 31 |
self._initialize_session_state()
|
| 32 |
-
|
| 33 |
-
# Set up project orchestrator
|
| 34 |
-
self._setup_project_orchestrator()
|
| 35 |
-
|
| 36 |
-
def _setup_project_orchestrator(self):
|
| 37 |
-
"""
|
| 38 |
-
Initialize project orchestrator with proper error handling
|
| 39 |
-
"""
|
| 40 |
-
try:
|
| 41 |
-
if 'project_orchestrator' not in st.session_state or st.session_state.project_orchestrator is None:
|
| 42 |
-
st.session_state.project_orchestrator = BookWritingOrchestrator()
|
| 43 |
-
except ValueError as ve:
|
| 44 |
-
st.error(f"API Key Error: {str(ve)}")
|
| 45 |
-
st.info("Please ensure your API keys are properly set in the environment variables.")
|
| 46 |
-
st.session_state.project_orchestrator = None
|
| 47 |
-
except Exception as e:
|
| 48 |
-
st.error(f"Error setting up project orchestrator: {e}")
|
| 49 |
-
st.session_state.project_orchestrator = None
|
| 50 |
-
|
| 51 |
def _initialize_session_state(self):
|
| 52 |
-
"""
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
if '
|
| 57 |
-
st.session_state.
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
# Ensure core session state variables exist
|
| 64 |
-
session_defaults = {
|
| 65 |
-
'project_orchestrator': None,
|
| 66 |
-
'book_concept': None,
|
| 67 |
-
'generated_chapters': {},
|
| 68 |
-
'total_chapters': 10, # Default value
|
| 69 |
-
'project_metadata': {
|
| 70 |
-
'title': 'Untitled Project',
|
| 71 |
-
'genre': 'Unspecified',
|
| 72 |
-
'total_chapters': 10
|
| 73 |
}
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
try:
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
-
if
|
| 88 |
-
|
| 89 |
-
st.session_state.
|
| 90 |
-
st.
|
| 91 |
-
st.session_state.generated_chapters = loaded_project.get('chapters', {})
|
| 92 |
-
st.session_state.total_chapters = loaded_project.get('total_chapters', 10)
|
| 93 |
-
st.session_state.project_metadata = {
|
| 94 |
-
'title': loaded_project.get('title', 'Untitled Project'),
|
| 95 |
-
'genre': loaded_project.get('genre', 'Unspecified'),
|
| 96 |
-
'total_chapters': loaded_project.get('total_chapters', 10)
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
st.success(f"Loaded project: {loaded_project.get('title', 'Untitled')}")
|
| 100 |
-
else:
|
| 101 |
-
st.warning("Could not load project.")
|
| 102 |
-
|
| 103 |
-
except Exception as e:
|
| 104 |
-
st.error(f"Error loading project: {e}")
|
| 105 |
-
|
| 106 |
-
def _save_current_project(self):
|
| 107 |
-
"""
|
| 108 |
-
Save the current project to persistent storage
|
| 109 |
-
"""
|
| 110 |
-
try:
|
| 111 |
-
# Prepare project data for saving
|
| 112 |
-
project_data = {
|
| 113 |
-
'project_id': st.session_state.active_project_id or str(uuid.uuid4()),
|
| 114 |
-
'title': st.session_state.project_metadata.get('title', 'Untitled Project'),
|
| 115 |
-
'genre': st.session_state.project_metadata.get('genre', 'Unspecified'),
|
| 116 |
-
'book_concept': st.session_state.book_concept,
|
| 117 |
-
'chapters': st.session_state.generated_chapters,
|
| 118 |
-
'total_chapters': st.session_state.total_chapters
|
| 119 |
-
}
|
| 120 |
|
| 121 |
-
# Save
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
-
if project_id:
|
| 125 |
-
# Update active project ID
|
| 126 |
-
st.session_state.active_project_id = project_id
|
| 127 |
-
st.success(f"Project saved successfully!")
|
| 128 |
-
else:
|
| 129 |
-
st.error("Failed to save project.")
|
| 130 |
-
|
| 131 |
except Exception as e:
|
| 132 |
-
st.error(f"Error saving
|
| 133 |
-
|
| 134 |
-
def
|
| 135 |
-
"""
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
if st.sidebar.button("Start New Project"):
|
| 142 |
-
# Reset session state
|
| 143 |
-
st.session_state.active_project_id = None
|
| 144 |
-
st.session_state.book_concept = None
|
| 145 |
-
st.session_state.generated_chapters = {}
|
| 146 |
-
st.session_state.total_chapters = 10
|
| 147 |
-
st.session_state.project_metadata = {
|
| 148 |
-
'title': 'Untitled Project',
|
| 149 |
-
'genre': 'Unspecified',
|
| 150 |
-
'total_chapters': 10
|
| 151 |
-
}
|
| 152 |
-
st.experimental_rerun()
|
| 153 |
-
|
| 154 |
-
# Load Project
|
| 155 |
-
project_list = self.persistence_manager.list_projects()
|
| 156 |
-
if project_list:
|
| 157 |
-
selected_project = st.sidebar.selectbox(
|
| 158 |
-
"Load Existing Project",
|
| 159 |
-
options=[p['title'] for p in project_list],
|
| 160 |
-
index=None,
|
| 161 |
-
placeholder="Select a project..."
|
| 162 |
-
)
|
| 163 |
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
None
|
| 169 |
-
)
|
| 170 |
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
tab1, tab2, tab3 = st.tabs([
|
| 184 |
-
"Book Concept",
|
| 185 |
-
"Chapter Generation",
|
| 186 |
-
"Project Progress"
|
| 187 |
-
])
|
| 188 |
-
|
| 189 |
-
with tab1:
|
| 190 |
-
self._render_concept_development()
|
| 191 |
-
|
| 192 |
-
with tab2:
|
| 193 |
-
self._render_chapter_generation()
|
| 194 |
-
|
| 195 |
-
with tab3:
|
| 196 |
-
self._render_project_progress()
|
| 197 |
-
|
| 198 |
-
def _render_concept_development(self):
|
| 199 |
-
"""
|
| 200 |
-
Render the book concept development interface
|
| 201 |
-
"""
|
| 202 |
-
st.header("📘 Book Concept Development")
|
| 203 |
-
|
| 204 |
-
# Initial concept input
|
| 205 |
-
initial_concept = st.text_area(
|
| 206 |
-
"Describe Your Book Idea",
|
| 207 |
-
height=200,
|
| 208 |
-
placeholder="Enter a comprehensive description of your book concept..."
|
| 209 |
-
)
|
| 210 |
-
|
| 211 |
-
if st.button("Generate Book Concept"):
|
| 212 |
-
if st.session_state.project_orchestrator is None:
|
| 213 |
-
st.error("Project orchestrator is not properly initialized. Please check your API keys.")
|
| 214 |
-
return
|
| 215 |
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
book_concept = st.session_state.project_orchestrator.generate_book_concept(
|
| 220 |
-
initial_concept
|
| 221 |
-
)
|
| 222 |
-
|
| 223 |
-
if book_concept:
|
| 224 |
-
# Update session state
|
| 225 |
-
st.session_state.book_concept = book_concept
|
| 226 |
-
st.session_state.project_metadata.update({
|
| 227 |
-
'title': book_concept.get('title', 'Untitled Project'),
|
| 228 |
-
'genre': book_concept.get('genre', 'Unspecified')
|
| 229 |
-
})
|
| 230 |
-
|
| 231 |
-
# Display generated concept
|
| 232 |
-
st.subheader("Generated Book Concept")
|
| 233 |
-
st.json(book_concept)
|
| 234 |
-
else:
|
| 235 |
-
st.error("Failed to generate book concept.")
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
return
|
| 250 |
|
| 251 |
-
|
| 252 |
-
col1, col2 = st.columns(2)
|
| 253 |
|
| 254 |
with col1:
|
| 255 |
-
#
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
min_value=1,
|
| 261 |
-
max_value=max_chapters,
|
| 262 |
-
value=min(current_chapter, max_chapters)
|
| 263 |
)
|
|
|
|
|
|
|
|
|
|
| 264 |
|
| 265 |
with col2:
|
| 266 |
-
|
| 267 |
-
total_chapters = st.number_input(
|
| 268 |
-
"Total Planned Chapters",
|
| 269 |
-
min_value=1,
|
| 270 |
-
max_value=max_chapters,
|
| 271 |
-
value=st.session_state.total_chapters
|
| 272 |
-
)
|
| 273 |
|
| 274 |
-
#
|
| 275 |
-
if
|
| 276 |
-
st.session_state.
|
| 277 |
-
st.
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
if st.session_state.project_orchestrator is None:
|
| 282 |
-
st.error("Project orchestrator is not properly initialized. Please check your API keys.")
|
| 283 |
-
return
|
| 284 |
-
|
| 285 |
-
with st.spinner(f"Generating Chapter {chapter_number}..."):
|
| 286 |
try:
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
st.session_state.book_concept,
|
| 290 |
-
chapter_number
|
| 291 |
-
)
|
| 292 |
-
|
| 293 |
-
if chapter_content:
|
| 294 |
-
# Store generated chapter
|
| 295 |
-
st.session_state.generated_chapters[chapter_number] = {
|
| 296 |
-
'content': chapter_content,
|
| 297 |
-
'status': 'Generated'
|
| 298 |
-
}
|
| 299 |
-
|
| 300 |
-
# Display Chapter Content
|
| 301 |
-
st.subheader(f"Chapter {chapter_number}")
|
| 302 |
-
st.write(chapter_content)
|
| 303 |
-
|
| 304 |
-
# Create a unique key for the text area
|
| 305 |
-
edit_key = f"edit_chapter_{chapter_number}"
|
| 306 |
-
|
| 307 |
-
# Optional: Edit Chapter
|
| 308 |
-
edited_content = st.text_area(
|
| 309 |
-
"Edit Chapter Content",
|
| 310 |
-
value=chapter_content,
|
| 311 |
-
height=400,
|
| 312 |
-
key=edit_key
|
| 313 |
-
)
|
| 314 |
-
|
| 315 |
-
# Save button with unique key
|
| 316 |
-
save_key = f"save_chapter_{chapter_number}"
|
| 317 |
-
if st.button(f"Save Chapter {chapter_number}", key=save_key):
|
| 318 |
-
st.session_state.generated_chapters[chapter_number]['content'] = edited_content
|
| 319 |
-
st.success(f"Chapter {chapter_number} saved!")
|
| 320 |
-
else:
|
| 321 |
-
st.error("Failed to generate chapter content.")
|
| 322 |
-
|
| 323 |
except Exception as e:
|
| 324 |
-
st.error(f"Error
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
|
| 332 |
-
#
|
| 333 |
-
st.
|
| 334 |
-
|
| 335 |
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
st.metric("Genre", st.session_state.project_metadata.get('genre', 'Unspecified'))
|
| 339 |
|
| 340 |
-
with
|
| 341 |
-
|
| 342 |
-
generated_chapters = len(st.session_state.generated_chapters)
|
| 343 |
|
| 344 |
-
|
| 345 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
|
| 347 |
-
#
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
chapter_tabs = st.tabs([
|
| 357 |
-
f"Chapter {ch_num}" for ch_num in sorted(st.session_state.generated_chapters.keys())
|
| 358 |
-
])
|
| 359 |
|
| 360 |
-
|
| 361 |
-
with
|
| 362 |
-
|
| 363 |
-
st.
|
|
|
|
| 364 |
|
| 365 |
-
#
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
"
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
st.error(f"Error exporting chapter: {e}")
|
| 387 |
else:
|
| 388 |
-
|
| 389 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
def run(self):
|
| 391 |
-
"""
|
| 392 |
-
|
| 393 |
-
"""
|
| 394 |
-
# Render project management sidebar
|
| 395 |
-
self.render_project_management()
|
| 396 |
|
| 397 |
-
# Main
|
| 398 |
-
|
|
|
|
|
|
|
| 399 |
|
| 400 |
-
|
| 401 |
-
if st.session_state.project_orchestrator is None:
|
| 402 |
-
st.warning("Please ensure your API keys are properly set in the environment variables.")
|
| 403 |
|
| 404 |
-
# Render
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
def main():
|
| 408 |
-
|
| 409 |
-
Initialize and run the application
|
| 410 |
-
"""
|
| 411 |
-
app = BookWritingApp()
|
| 412 |
app.run()
|
| 413 |
|
| 414 |
if __name__ == "__main__":
|
|
|
|
| 1 |
# File: app.py
|
|
|
|
|
|
|
| 2 |
|
| 3 |
import streamlit as st
|
|
|
|
| 4 |
import json
|
| 5 |
+
import os
|
| 6 |
+
from selfapi_writer import SelfApiWriter
|
| 7 |
+
from data_manager import DataManager
|
|
|
|
| 8 |
|
| 9 |
+
class SelfApiApp:
|
| 10 |
def __init__(self):
|
| 11 |
+
"""Initialize the Streamlit application"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
st.set_page_config(
|
| 13 |
+
page_title="Self.api Book Generator",
|
| 14 |
+
page_icon="📚",
|
| 15 |
layout="wide"
|
| 16 |
)
|
| 17 |
|
| 18 |
+
# Initialize data manager
|
| 19 |
+
self.data_manager = DataManager()
|
| 20 |
|
| 21 |
# Initialize session state
|
| 22 |
self._initialize_session_state()
|
| 23 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
def _initialize_session_state(self):
|
| 25 |
+
"""Initialize session state variables"""
|
| 26 |
+
if 'current_tab' not in st.session_state:
|
| 27 |
+
st.session_state.current_tab = 'Blueprint'
|
| 28 |
+
|
| 29 |
+
if 'blueprint' not in st.session_state:
|
| 30 |
+
st.session_state.blueprint = self.default_blueprint
|
| 31 |
+
|
| 32 |
+
if 'book_content' not in st.session_state:
|
| 33 |
+
st.session_state.book_content = {
|
| 34 |
+
"introduction": "",
|
| 35 |
+
"parts": []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
}
|
| 37 |
+
|
| 38 |
+
@property
|
| 39 |
+
def default_blueprint(self):
|
| 40 |
+
"""Get default blueprint content"""
|
| 41 |
+
return """# Editorial Blueprint: Self.api - A Modern Spiritual Operating System
|
| 42 |
+
|
| 43 |
+
## Core Editorial Vision
|
| 44 |
+
Transform "Self.api" from a conceptual framework into a revolutionary guide that speaks to the modern seeker - the stressed executive who downloads meditation apps but can't stick to them, the engineering lead who understands distributed systems better than their own emotions, and the consultant who can optimize anything except their own life satisfaction.
|
| 45 |
+
|
| 46 |
+
### Target Reader Profile
|
| 47 |
+
- Primary: Tech professionals, entrepreneurs, and knowledge workers (25-45)
|
| 48 |
+
- Secondary: Anyone feeling disconnected in our hyper-connected world
|
| 49 |
+
- Psychographic: Analytical minds seeking spiritual depth without the woo-woo
|
| 50 |
+
|
| 51 |
+
## Book Structure
|
| 52 |
+
### Introduction: "System Requirements: A Human's Guide to Being Human"
|
| 53 |
+
- Opening hook: "In a world where we can Google anything except our own purpose..."
|
| 54 |
+
- Key narrative: Author's journey from debugging code to debugging consciousness
|
| 55 |
+
|
| 56 |
+
### Part 1: The Human Input/Output System
|
| 57 |
+
- Rate Limiting Your Reality
|
| 58 |
+
- Debugging Your Attention Span
|
| 59 |
+
- The Mindfulness Microservice
|
| 60 |
+
|
| 61 |
+
### Part 2: Your Internal Neural Network
|
| 62 |
+
- Training Your Gut Algorithm
|
| 63 |
+
- The Wisdom Cache
|
| 64 |
+
- Pattern Recognition Beyond Logic
|
| 65 |
+
|
| 66 |
+
### Part 3: The Source Code of Being
|
| 67 |
+
- Quantum Mechanics of Consciousness
|
| 68 |
+
- The Purpose Protocol
|
| 69 |
+
- Refactoring Your Reality
|
| 70 |
+
|
| 71 |
+
### Part 4: The Universal Runtime Environment
|
| 72 |
+
- Distributed Consciousness Systems
|
| 73 |
+
- The Empathy Protocol
|
| 74 |
+
- Scaling Your Impact
|
| 75 |
+
|
| 76 |
+
## Style Guidelines
|
| 77 |
+
1. Technical Authenticity
|
| 78 |
+
- Use API and tech metaphors naturally
|
| 79 |
+
- Example: "Think of meditation as a daily health check for your consciousness microservices"
|
| 80 |
+
|
| 81 |
+
2. Voice & Tone
|
| 82 |
+
- Witty But Wise
|
| 83 |
+
- Blend humor with depth
|
| 84 |
+
- Style: Think Douglas Adams meets Deepak Chopra
|
| 85 |
+
|
| 86 |
+
3. Chapter Structure
|
| 87 |
+
- System Log (personal story)
|
| 88 |
+
- Documentation (teaching)
|
| 89 |
+
- Implementation Guide (practical steps)
|
| 90 |
+
|
| 91 |
+
## Content Requirements
|
| 92 |
+
1. Each chapter should have:
|
| 93 |
+
- 3 "aha" moments
|
| 94 |
+
- 2-3 quotable passages
|
| 95 |
+
- Practical exercises every 5 pages
|
| 96 |
+
- Balance: 60% practical, 40% philosophical
|
| 97 |
+
- Links to both ancient wisdom and modern science"""
|
| 98 |
+
|
| 99 |
+
def save_current_state(self):
|
| 100 |
+
"""Save current state to data directory"""
|
| 101 |
try:
|
| 102 |
+
# Save blueprint if it exists
|
| 103 |
+
if st.session_state.blueprint:
|
| 104 |
+
blueprint_file = self.data_manager.save_blueprint(st.session_state.blueprint)
|
| 105 |
+
st.success(f"Blueprint saved: {blueprint_file}")
|
| 106 |
|
| 107 |
+
# Save book content if it exists
|
| 108 |
+
if st.session_state.book_content:
|
| 109 |
+
content_file = self.data_manager.save_book_content(st.session_state.book_content)
|
| 110 |
+
st.success(f"Book content saved: {content_file}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
+
# Save individual chapters
|
| 113 |
+
for part_idx, part in enumerate(st.session_state.book_content.get('parts', [])):
|
| 114 |
+
for ch_idx, chapter in enumerate(part.get('chapters', [])):
|
| 115 |
+
if chapter.get('content'):
|
| 116 |
+
self.data_manager.save_chapter(part_idx, ch_idx, chapter)
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
except Exception as e:
|
| 119 |
+
st.error(f"Error saving state: {e}")
|
| 120 |
+
|
| 121 |
+
def load_latest_state(self):
|
| 122 |
+
"""Load latest state from data directory"""
|
| 123 |
+
try:
|
| 124 |
+
# Load latest blueprint
|
| 125 |
+
blueprint = self.data_manager.load_latest_blueprint()
|
| 126 |
+
if blueprint:
|
| 127 |
+
st.session_state.blueprint = blueprint
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
# Load latest book content
|
| 130 |
+
content = self.data_manager.load_latest_book_content()
|
| 131 |
+
if content:
|
| 132 |
+
st.session_state.book_content = content
|
|
|
|
|
|
|
| 133 |
|
| 134 |
+
st.success("Latest state loaded successfully!")
|
| 135 |
+
|
| 136 |
+
except Exception as e:
|
| 137 |
+
st.error(f"Error loading state: {e}")
|
| 138 |
+
|
| 139 |
+
def export_book(self):
|
| 140 |
+
"""Export book in markdown format"""
|
| 141 |
+
try:
|
| 142 |
+
# Export as markdown
|
| 143 |
+
if st.session_state.book_content:
|
| 144 |
+
filepath = self.data_manager.export_markdown(st.session_state.book_content)
|
| 145 |
+
st.success(f"Book exported to: {filepath}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
+
# Create download link
|
| 148 |
+
with open(filepath, 'r') as f:
|
| 149 |
+
markdown_content = f.read()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
+
st.download_button(
|
| 152 |
+
label="Download Markdown",
|
| 153 |
+
data=markdown_content,
|
| 154 |
+
file_name="self_api_book.md",
|
| 155 |
+
mime="text/markdown"
|
| 156 |
+
)
|
| 157 |
+
except Exception as e:
|
| 158 |
+
st.error(f"Error exporting book: {e}")
|
| 159 |
+
|
| 160 |
+
def render_blueprint_editor(self):
|
| 161 |
+
"""Render the blueprint editor interface"""
|
| 162 |
+
st.header("�� Editorial Blueprint Editor")
|
|
|
|
| 163 |
|
| 164 |
+
col1, col2 = st.columns([7, 3])
|
|
|
|
| 165 |
|
| 166 |
with col1:
|
| 167 |
+
# Blueprint Editor
|
| 168 |
+
edited_blueprint = st.text_area(
|
| 169 |
+
"Edit Blueprint",
|
| 170 |
+
value=st.session_state.blueprint,
|
| 171 |
+
height=600
|
|
|
|
|
|
|
|
|
|
| 172 |
)
|
| 173 |
+
|
| 174 |
+
if edited_blueprint != st.session_state.blueprint:
|
| 175 |
+
st.session_state.blueprint = edited_blueprint
|
| 176 |
|
| 177 |
with col2:
|
| 178 |
+
st.subheader("Blueprint Controls")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
+
# Reset Blueprint
|
| 181 |
+
if st.button("Reset to Default"):
|
| 182 |
+
st.session_state.blueprint = self.default_blueprint
|
| 183 |
+
st.experimental_rerun()
|
| 184 |
+
|
| 185 |
+
# Save Blueprint
|
| 186 |
+
if st.button("Save Blueprint"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
try:
|
| 188 |
+
filename = self.data_manager.save_blueprint(st.session_state.blueprint)
|
| 189 |
+
st.success(f"Blueprint saved as: {filename}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
except Exception as e:
|
| 191 |
+
st.error(f"Error saving blueprint: {e}")
|
| 192 |
+
|
| 193 |
+
# Load Blueprint
|
| 194 |
+
if st.button("Load Latest"):
|
| 195 |
+
try:
|
| 196 |
+
blueprint = self.data_manager.load_latest_blueprint()
|
| 197 |
+
if blueprint:
|
| 198 |
+
st.session_state.blueprint = blueprint
|
| 199 |
+
st.success("Latest blueprint loaded!")
|
| 200 |
+
st.experimental_rerun()
|
| 201 |
+
except Exception as e:
|
| 202 |
+
st.error(f"Error loading blueprint: {e}")
|
| 203 |
+
|
| 204 |
+
def render_generator_interface(self):
|
| 205 |
+
"""Render the book generation interface"""
|
| 206 |
+
st.header("⚙️ Content Generator")
|
| 207 |
|
| 208 |
+
# Initialize writer if needed
|
| 209 |
+
if 'writer' not in st.session_state:
|
| 210 |
+
st.session_state.writer = SelfApiWriter()
|
| 211 |
|
| 212 |
+
# Generation controls
|
| 213 |
+
col1, col2 = st.columns([2, 1])
|
|
|
|
| 214 |
|
| 215 |
+
with col1:
|
| 216 |
+
st.subheader("Generation Controls")
|
|
|
|
| 217 |
|
| 218 |
+
# Introduction Generation
|
| 219 |
+
intro_col1, intro_col2 = st.columns([3, 1])
|
| 220 |
+
with intro_col1:
|
| 221 |
+
st.markdown("### Introduction")
|
| 222 |
+
with intro_col2:
|
| 223 |
+
if st.button("Generate Introduction"):
|
| 224 |
+
with st.spinner("Generating introduction..."):
|
| 225 |
+
intro_content = st.session_state.writer.write_introduction()
|
| 226 |
+
st.session_state.book_content["introduction"] = intro_content
|
| 227 |
+
self.save_current_state()
|
| 228 |
+
st.success("Introduction generated!")
|
| 229 |
|
| 230 |
+
# Parts and Chapters Generation
|
| 231 |
+
for part_idx, part in enumerate(st.session_state.writer.book_structure["parts"]):
|
| 232 |
+
st.markdown(f"### Part {part_idx + 1}: {part['title']}")
|
| 233 |
+
|
| 234 |
+
for ch_idx, ch_title in enumerate(part["chapters"]):
|
| 235 |
+
ch_col1, ch_col2 = st.columns([3, 1])
|
| 236 |
+
with ch_col1:
|
| 237 |
+
st.markdown(f"- {ch_title}")
|
| 238 |
+
with ch_col2:
|
| 239 |
+
if st.button(f"Generate", key=f"gen_{part_idx}_{ch_idx}"):
|
| 240 |
+
with st.spinner(f"Generating {ch_title}..."):
|
| 241 |
+
chapter_content = st.session_state.writer.write_chapter(part_idx, ch_idx)
|
| 242 |
+
|
| 243 |
+
# Ensure part exists
|
| 244 |
+
while len(st.session_state.book_content["parts"]) <= part_idx:
|
| 245 |
+
st.session_state.book_content["parts"].append({
|
| 246 |
+
"title": part["title"],
|
| 247 |
+
"chapters": []
|
| 248 |
+
})
|
| 249 |
+
|
| 250 |
+
# Ensure chapter exists
|
| 251 |
+
while len(st.session_state.book_content["parts"][part_idx]["chapters"]) <= ch_idx:
|
| 252 |
+
st.session_state.book_content["parts"][part_idx]["chapters"].append({
|
| 253 |
+
"title": "",
|
| 254 |
+
"content": ""
|
| 255 |
+
})
|
| 256 |
+
|
| 257 |
+
# Update chapter
|
| 258 |
+
st.session_state.book_content["parts"][part_idx]["chapters"][ch_idx] = {
|
| 259 |
+
"title": ch_title,
|
| 260 |
+
"content": chapter_content
|
| 261 |
+
}
|
| 262 |
+
self.save_current_state()
|
| 263 |
+
st.success(f"{ch_title} generated!")
|
| 264 |
|
| 265 |
+
with col2:
|
| 266 |
+
st.subheader("Quick Actions")
|
|
|
|
|
|
|
|
|
|
| 267 |
|
| 268 |
+
if st.button("Generate All"):
|
| 269 |
+
with st.spinner("Generating entire book..."):
|
| 270 |
+
# Generate introduction
|
| 271 |
+
intro_content = st.session_state.writer.write_introduction()
|
| 272 |
+
st.session_state.book_content["introduction"] = intro_content
|
| 273 |
|
| 274 |
+
# Generate all chapters
|
| 275 |
+
for part_idx, part in enumerate(st.session_state.writer.book_structure["parts"]):
|
| 276 |
+
# Initialize part
|
| 277 |
+
if len(st.session_state.book_content["parts"]) <= part_idx:
|
| 278 |
+
st.session_state.book_content["parts"].append({
|
| 279 |
+
"title": part["title"],
|
| 280 |
+
"chapters": []
|
| 281 |
+
})
|
| 282 |
+
|
| 283 |
+
for ch_idx, ch_title in enumerate(part["chapters"]):
|
| 284 |
+
chapter_content = st.session_state.writer.write_chapter(part_idx, ch_idx)
|
| 285 |
+
|
| 286 |
+
# Ensure chapter exists
|
| 287 |
+
while len(st.session_state.book_content["parts"][part_idx]["chapters"]) <= ch_idx:
|
| 288 |
+
st.session_state.book_content["parts"][part_idx]["chapters"].append({
|
| 289 |
+
"title": "",
|
| 290 |
+
"content": ""
|
| 291 |
+
})
|
| 292 |
+
|
| 293 |
+
# Update chapter
|
| 294 |
+
st.session_state.book_content["parts"][part_idx]["chapters"][ch_idx] = {
|
| 295 |
+
"title": ch_title,
|
| 296 |
+
"content": chapter_content
|
| 297 |
+
}
|
| 298 |
|
| 299 |
+
self.save_current_state()
|
| 300 |
+
st.success("Full book generated!")
|
| 301 |
+
|
| 302 |
+
def render_content_preview(self):
|
| 303 |
+
"""Render the book content preview"""
|
| 304 |
+
st.header("📚 Generated Content Preview")
|
| 305 |
+
|
| 306 |
+
# Display any generated content here
|
| 307 |
+
if not st.session_state.book_content["introduction"] and not st.session_state.book_content["parts"]:
|
| 308 |
+
st.info("No content generated yet. Use the Generator tab to create content.")
|
|
|
|
| 309 |
else:
|
| 310 |
+
# Add export button
|
| 311 |
+
if st.button("Export Current Content"):
|
| 312 |
+
self.export_book()
|
| 313 |
+
|
| 314 |
+
# Show introduction if it exists
|
| 315 |
+
if st.session_state.book_content["introduction"]:
|
| 316 |
+
st.subheader("Introduction")
|
| 317 |
+
with st.expander("View Introduction", expanded=True):
|
| 318 |
+
st.markdown(st.session_state.book_content["introduction"])
|
| 319 |
+
|
| 320 |
+
# Show generated parts and chapters
|
| 321 |
+
for part_idx, part in enumerate(st.session_state.book_content["parts"]):
|
| 322 |
+
st.subheader(f"Part {part_idx + 1}: {part['title']}")
|
| 323 |
+
for chapter in part["chapters"]:
|
| 324 |
+
if chapter["content"]:
|
| 325 |
+
with st.expander(f"Chapter: {chapter['title']}", expanded=False):
|
| 326 |
+
st.markdown(chapter["content"])
|
| 327 |
+
|
| 328 |
def run(self):
|
| 329 |
+
"""Run the Streamlit application"""
|
| 330 |
+
st.title("🚀 Self.api Book Generator")
|
|
|
|
|
|
|
|
|
|
| 331 |
|
| 332 |
+
# Main navigation
|
| 333 |
+
tabs = ["Blueprint", "Generator", "Preview"]
|
| 334 |
+
selected_tab = st.radio("Navigation", tabs, horizontal=True)
|
| 335 |
+
st.session_state.current_tab = selected_tab
|
| 336 |
|
| 337 |
+
st.divider()
|
|
|
|
|
|
|
| 338 |
|
| 339 |
+
# Render appropriate content based on selected tab
|
| 340 |
+
if selected_tab == "Blueprint":
|
| 341 |
+
self.render_blueprint_editor()
|
| 342 |
+
elif selected_tab == "Generator":
|
| 343 |
+
self.render_generator_interface()
|
| 344 |
+
else:
|
| 345 |
+
self.render_content_preview()
|
| 346 |
|
| 347 |
def main():
|
| 348 |
+
app = SelfApiApp()
|
|
|
|
|
|
|
|
|
|
| 349 |
app.run()
|
| 350 |
|
| 351 |
if __name__ == "__main__":
|